diff options
Diffstat (limited to 'modules')
252 files changed, 11470 insertions, 6355 deletions
diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp index 3d66b27556..e467ed60ee 100644 --- a/modules/etcpak/image_compress_etcpak.cpp +++ b/modules/etcpak/image_compress_etcpak.cpp @@ -111,13 +111,16 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img, float p_lossy_qua Image::Format target_format = Image::FORMAT_RGBA8; if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC1) { target_format = Image::FORMAT_ETC; + r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2) { target_format = Image::FORMAT_ETC2_RGB8; + r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) { target_format = Image::FORMAT_ETC2_RA_AS_RG; r_img->convert_rg_to_ra_rgba8(); } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_ALPHA) { target_format = Image::FORMAT_ETC2_RGBA8; + r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT1) { target_format = Image::FORMAT_DXT1; } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG) { diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index c8eda53a2d..4981750b7d 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -547,7 +547,7 @@ <return type="void" /> <param index="0" name="icon_path" type="String" /> <description> - Add a custom icon to the current script. After loading an icon at [param icon_path], the icon is displayed in the Scene dock for every node that the script is attached to. For named classes, the icon is also displayed in various editor dialogs. + Add a custom icon to the current script. The script must be registered as a global class using the [code]class_name[/code] keyword for this to have a visible effect. The icon specified at [param icon_path] is displayed in the Scene dock for every node of that class, as well as in various editor dialogs. [codeblock] @icon("res://path/to/class/icon.svg") [/codeblock] diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 420401cb79..0ebdbf090d 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -322,7 +322,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } String word = str.substr(j, to - j); - Color col = Color(); + Color col; if (global_functions.has(word)) { // "assert" and "preload" are reserved, so highlight even if not followed by a bracket. if (word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::ASSERT) || word == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::PRELOAD)) { diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index b4da9c1224..91f31174dd 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -478,7 +478,7 @@ void GDScript::_clear_doc() { void GDScript::_update_doc() { _clear_doc(); - doc.script_path = "\"" + get_path().get_slice("://", 1) + "\""; + doc.script_path = vformat(R"("%s")", get_script_path().get_slice("://", 1)); if (!name.is_empty()) { doc.name = name; } else { @@ -629,10 +629,6 @@ void GDScript::_update_doc() { } } - for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { - E.value->_update_doc(); - } - _add_doc(doc); } #endif @@ -674,36 +670,14 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc base_cache = Ref<GDScript>(); } - if (c->extends_used) { - String ext_path = ""; - if (String(c->extends_path) != "" && String(c->extends_path) != get_path()) { - ext_path = c->extends_path; - if (ext_path.is_relative_path()) { - String base_path = get_path(); - if (base_path.is_empty() || base_path.is_relative_path()) { - ERR_PRINT(("Could not resolve relative path for parent class: " + ext_path).utf8().get_data()); - } else { - ext_path = base_path.get_base_dir().path_join(ext_path); - } - } - } else if (c->extends.size() != 0) { - const StringName &base_class = c->extends[0]; - - if (ScriptServer::is_global_class(base_class)) { - ext_path = ScriptServer::get_global_class_path(base_class); - } - } - - if (!ext_path.is_empty()) { - if (ext_path != get_path()) { - Ref<GDScript> bf = ResourceLoader::load(ext_path); - - if (bf.is_valid()) { - base_cache = bf; - bf->inheriters_cache.insert(get_instance_id()); - } - } else { - ERR_PRINT(("Path extending itself in " + ext_path).utf8().get_data()); + GDScriptParser::DataType base_type = parser.get_tree()->base_type; + if (base_type.kind == GDScriptParser::DataType::CLASS) { + Ref<GDScript> bf = GDScriptCache::get_full_script(base_type.script_path, err, path); + if (err == OK) { + bf = Ref<GDScript>(bf->find_class(base_type.class_type->fqcn)); + if (bf.is_valid()) { + base_cache = bf; + bf->inheriters_cache.insert(get_instance_id()); } } } @@ -727,6 +701,7 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc Variant default_value; if (member.variable->initializer && member.variable->initializer->is_constant) { default_value = member.variable->initializer->reduced_value; + GDScriptCompiler::convert_to_initializer_type(default_value, member.variable); } member_default_values_cache[member.variable->identifier->name] = default_value; } break; @@ -825,22 +800,20 @@ void GDScript::update_exports() { #endif } -void GDScript::_set_subclass_path(Ref<GDScript> &p_sc, const String &p_path) { - p_sc->path = p_path; - for (KeyValue<StringName, Ref<GDScript>> &E : p_sc->subclasses) { - _set_subclass_path(E.value, p_path); - } -} - String GDScript::_get_debug_path() const { if (is_built_in() && !get_name().is_empty()) { - return get_name() + " (" + get_path() + ")"; + return vformat("%s(%s)", get_name(), get_script_path()); } else { - return get_path(); + return get_script_path(); } } Error GDScript::reload(bool p_keep_state) { + if (reloading) { + return OK; + } + reloading = true; + bool has_instances; { MutexLock lock(GDScriptLanguage::singleton->mutex); @@ -860,9 +833,10 @@ Error GDScript::reload(bool p_keep_state) { basedir = basedir.get_base_dir(); } -// Loading a template, don't parse. + // Loading a template, don't parse. #ifdef TOOLS_ENABLED if (EditorPaths::get_singleton() && basedir.begins_with(EditorPaths::get_singleton()->get_project_script_templates_dir())) { + reloading = false; return OK; } #endif @@ -872,11 +846,10 @@ Error GDScript::reload(bool p_keep_state) { if (source_path.is_empty()) { source_path = get_path(); } - if (!source_path.is_empty()) { - MutexLock lock(GDScriptCache::singleton->lock); - if (!GDScriptCache::singleton->shallow_gdscript_cache.has(source_path)) { - GDScriptCache::singleton->shallow_gdscript_cache[source_path] = this; - } + Ref<GDScript> cached_script = GDScriptCache::get_cached_script(source_path); + if (!source_path.is_empty() && cached_script.is_null()) { + MutexLock lock(GDScriptCache::singleton->mutex); + GDScriptCache::singleton->shallow_gdscript_cache[source_path] = Ref<GDScript>(this); } } @@ -889,6 +862,7 @@ Error GDScript::reload(bool p_keep_state) { } // TODO: Show all error messages. _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), parser.get_errors().front()->get().line, ("Parse Error: " + parser.get_errors().front()->get().message).utf8().get_data(), false, ERR_HANDLER_SCRIPT); + reloading = false; return ERR_PARSE_ERROR; } @@ -905,6 +879,7 @@ Error GDScript::reload(bool p_keep_state) { _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), e->get().line, ("Parse Error: " + e->get().message).utf8().get_data(), false, ERR_HANDLER_SCRIPT); e = e->next(); } + reloading = false; return ERR_PARSE_ERROR; } @@ -913,18 +888,16 @@ Error GDScript::reload(bool p_keep_state) { GDScriptCompiler compiler; err = compiler.compile(&parser, this, p_keep_state); -#ifdef TOOLS_ENABLED - _update_doc(); -#endif - if (err) { if (can_run) { if (EngineDebugger::is_active()) { GDScriptLanguage::get_singleton()->debug_break_parse(_get_debug_path(), compiler.get_error_line(), "Parser Error: " + compiler.get_error()); } _err_print_error("GDScript::reload", path.is_empty() ? "built-in" : (const char *)path.utf8().get_data(), compiler.get_error_line(), ("Compile Error: " + compiler.get_error()).utf8().get_data(), false, ERR_HANDLER_SCRIPT); + reloading = false; return ERR_COMPILATION_FAILED; } else { + reloading = false; return err; } } @@ -932,19 +905,12 @@ Error GDScript::reload(bool p_keep_state) { for (const GDScriptWarning &warning : parser.get_warnings()) { if (EngineDebugger::is_active()) { Vector<ScriptLanguage::StackInfo> si; - EngineDebugger::get_script_debugger()->send_error("", get_path(), warning.start_line, warning.get_name(), warning.get_message(), false, ERR_HANDLER_WARNING, si); + EngineDebugger::get_script_debugger()->send_error("", get_script_path(), warning.start_line, warning.get_name(), warning.get_message(), false, ERR_HANDLER_WARNING, si); } } #endif - valid = true; - - for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { - _set_subclass_path(E.value, path); - } - - _init_rpc_methods_properties(); - + reloading = false; return OK; } @@ -1051,11 +1017,26 @@ Error GDScript::load_byte_code(const String &p_path) { } void GDScript::set_path(const String &p_path, bool p_take_over) { - Script::set_path(p_path, p_take_over); + String old_path = path; + if (is_root_script()) { + Script::set_path(p_path, p_take_over); + } this->path = p_path; + GDScriptCache::move_script(old_path, p_path); + for (KeyValue<StringName, Ref<GDScript>> &kv : subclasses) { + kv.value->set_path(p_path, p_take_over); + } +} + +String GDScript::get_script_path() const { + return path; } Error GDScript::load_source_code(const String &p_path) { + if (p_path.is_empty() || ResourceLoader::get_resource_type(p_path.get_slice("::", 0)) == "PackedScene") { + return OK; + } + Vector<uint8_t> sourcef; Error err; Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err); @@ -1127,6 +1108,124 @@ bool GDScript::inherits_script(const Ref<Script> &p_script) const { return false; } +GDScript *GDScript::find_class(const String &p_qualified_name) { + String first = p_qualified_name.get_slice("::", 0); + + GDScript *result = nullptr; + if (first.is_empty() || first == name) { + result = this; + } else if (first == get_root_script()->path) { + result = get_root_script(); + } else if (HashMap<StringName, Ref<GDScript>>::Iterator E = subclasses.find(first)) { + result = E->value.ptr(); + } else if (_owner != nullptr) { + // Check parent scope. + return _owner->find_class(p_qualified_name); + } + + int name_count = p_qualified_name.get_slice_count("::"); + for (int i = 1; result != nullptr && i < name_count; i++) { + String current_name = p_qualified_name.get_slice("::", i); + if (HashMap<StringName, Ref<GDScript>>::Iterator E = result->subclasses.find(current_name)) { + result = E->value.ptr(); + } else { + // Couldn't find inner class. + return nullptr; + } + } + + return result; +} + +bool GDScript::is_subclass(const GDScript *p_script) { + String fqn = p_script->fully_qualified_name; + if (!fqn.is_empty() && fqn != fully_qualified_name && fqn.begins_with(fully_qualified_name)) { + String fqn_rest = fqn.substr(fully_qualified_name.length()); + return find_class(fqn_rest) == p_script; + } + return false; +} + +GDScript *GDScript::get_root_script() { + GDScript *result = this; + while (result->_owner) { + result = result->_owner; + } + return result; +} + +RBSet<GDScript *> GDScript::get_dependencies() { + RBSet<GDScript *> dependencies; + + _get_dependencies(dependencies, this); + dependencies.erase(this); + + return dependencies; +} + +RBSet<GDScript *> GDScript::get_inverted_dependencies() { + RBSet<GDScript *> inverted_dependencies; + + List<GDScript *> scripts; + { + MutexLock lock(GDScriptLanguage::singleton->mutex); + + SelfList<GDScript> *elem = GDScriptLanguage::singleton->script_list.first(); + while (elem) { + scripts.push_back(elem->self()); + elem = elem->next(); + } + } + + for (GDScript *scr : scripts) { + if (scr == nullptr || scr == this || scr->destructing) { + continue; + } + + RBSet<GDScript *> scr_dependencies = scr->get_dependencies(); + if (scr_dependencies.has(this)) { + inverted_dependencies.insert(scr); + } + } + + return inverted_dependencies; +} + +RBSet<GDScript *> GDScript::get_must_clear_dependencies() { + RBSet<GDScript *> dependencies = get_dependencies(); + RBSet<GDScript *> must_clear_dependencies; + HashMap<GDScript *, RBSet<GDScript *>> inverted_dependencies; + + for (GDScript *E : dependencies) { + inverted_dependencies.insert(E, E->get_inverted_dependencies()); + } + + RBSet<GDScript *> cant_clear; + for (KeyValue<GDScript *, RBSet<GDScript *>> &E : inverted_dependencies) { + for (GDScript *F : E.value) { + if (!dependencies.has(F)) { + cant_clear.insert(E.key); + for (GDScript *G : E.key->get_dependencies()) { + cant_clear.insert(G); + } + break; + } + } + } + + for (KeyValue<GDScript *, RBSet<GDScript *>> &E : inverted_dependencies) { + if (cant_clear.has(E.key) || ScriptServer::is_global_class(E.key->get_fully_qualified_name())) { + continue; + } + must_clear_dependencies.insert(E.key); + } + + cant_clear.clear(); + dependencies.clear(); + inverted_dependencies.clear(); + return must_clear_dependencies; +} + bool GDScript::has_script_signal(const StringName &p_signal) const { if (_signals.has(p_signal)) { return true; @@ -1188,15 +1287,76 @@ String GDScript::_get_gdscript_reference_class_name(const GDScript *p_gdscript) return class_name; } +GDScript *GDScript::_get_gdscript_from_variant(const Variant &p_variant) { + Variant::Type type = p_variant.get_type(); + if (type != Variant::Type::OBJECT) + return nullptr; + + Object *obj = p_variant; + if (obj == nullptr) { + return nullptr; + } + + return Object::cast_to<GDScript>(obj); +} + +void GDScript::_get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except) { + if (skip_dependencies || p_dependencies.has(this)) { + return; + } + p_dependencies.insert(this); + + for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { + if (E.value == nullptr) { + continue; + } + for (const Variant &V : E.value->constants) { + GDScript *scr = _get_gdscript_from_variant(V); + if (scr != nullptr && scr != p_except) { + scr->_get_dependencies(p_dependencies, p_except); + } + } + } + + if (implicit_initializer) { + for (const Variant &V : implicit_initializer->constants) { + GDScript *scr = _get_gdscript_from_variant(V); + if (scr != nullptr && scr != p_except) { + scr->_get_dependencies(p_dependencies, p_except); + } + } + } + + if (implicit_ready) { + for (const Variant &V : implicit_ready->constants) { + GDScript *scr = _get_gdscript_from_variant(V); + if (scr != nullptr && scr != p_except) { + scr->_get_dependencies(p_dependencies, p_except); + } + } + } + + for (KeyValue<StringName, Ref<GDScript>> &E : subclasses) { + if (E.value != p_except) { + E.value->_get_dependencies(p_dependencies, p_except); + } + } + + for (const KeyValue<StringName, Variant> &E : constants) { + GDScript *scr = _get_gdscript_from_variant(E.value); + if (scr != nullptr && scr != p_except) { + scr->_get_dependencies(p_dependencies, p_except); + } + } +} + GDScript::GDScript() : script_list(this) { -#ifdef DEBUG_ENABLED { MutexLock lock(GDScriptLanguage::get_singleton()->mutex); GDScriptLanguage::get_singleton()->script_list.add(&script_list); } -#endif } void GDScript::_save_orphaned_subclasses() { @@ -1238,56 +1398,67 @@ void GDScript::_init_rpc_methods_properties() { rpc_config = base->rpc_config.duplicate(); } - GDScript *cscript = this; - HashMap<StringName, Ref<GDScript>>::Iterator sub_E = subclasses.begin(); - while (cscript) { - // RPC Methods - for (KeyValue<StringName, GDScriptFunction *> &E : cscript->member_functions) { - Variant config = E.value->get_rpc_config(); - if (config.get_type() != Variant::NIL) { - rpc_config[E.value->get_name()] = config; - } - } - - if (cscript != this) { - ++sub_E; - } - - if (sub_E) { - cscript = sub_E->value.ptr(); - } else { - cscript = nullptr; + // RPC Methods + for (KeyValue<StringName, GDScriptFunction *> &E : member_functions) { + Variant config = E.value->get_rpc_config(); + if (config.get_type() != Variant::NIL) { + rpc_config[E.value->get_name()] = config; } } } -GDScript::~GDScript() { - { - MutexLock lock(GDScriptLanguage::get_singleton()->mutex); +void GDScript::clear() { + if (clearing) { + return; + } + clearing = true; - while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) { - // Order matters since clearing the stack may already cause - // the GDSCriptFunctionState to be destroyed and thus removed from the list. - pending_func_states.remove(E); - E->self()->_clear_stack(); + RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies(); + HashMap<GDScript *, ObjectID> must_clear_dependencies_objectids; + + // Log the objectids before clearing, as a cascade of clear could + // remove instances that are still in the clear loop + for (GDScript *E : must_clear_dependencies) { + must_clear_dependencies_objectids.insert(E, E->get_instance_id()); + } + + for (GDScript *E : must_clear_dependencies) { + Object *obj = ObjectDB::get_instance(must_clear_dependencies_objectids[E]); + if (obj == nullptr) { + continue; } + + E->skip_dependencies = true; + E->clear(); + E->skip_dependencies = false; + GDScriptCache::remove_script(E->get_path()); } + RBSet<StringName> member_function_names; for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { - memdelete(E.value); + member_function_names.insert(E.key); + } + for (const StringName &E : member_function_names) { + if (member_functions.has(E)) { + memdelete(member_functions[E]); + } + } + member_function_names.clear(); + member_functions.clear(); + + for (KeyValue<StringName, GDScript::MemberInfo> &E : member_indices) { + E.value.data_type.script_type_ref = Ref<Script>(); } if (implicit_initializer) { memdelete(implicit_initializer); } + implicit_initializer = nullptr; if (implicit_ready) { memdelete(implicit_ready); } - - if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown. - GDScriptCache::remove_script(get_path()); - } + implicit_ready = nullptr; _save_orphaned_subclasses(); @@ -1297,14 +1468,37 @@ GDScript::~GDScript() { _clear_doc(); } #endif + clearing = false; +} + +GDScript::~GDScript() { + if (destructing) { + return; + } + destructing = true; + + clear(); + + { + MutexLock lock(GDScriptLanguage::get_singleton()->mutex); + + while (SelfList<GDScriptFunctionState> *E = pending_func_states.first()) { + // Order matters since clearing the stack may already cause + // the GDScriptFunctionState to be destroyed and thus removed from the list. + pending_func_states.remove(E); + E->self()->_clear_stack(); + } + } -#ifdef DEBUG_ENABLED { MutexLock lock(GDScriptLanguage::get_singleton()->mutex); GDScriptLanguage::get_singleton()->script_list.remove(&script_list); } -#endif + + if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown. + GDScriptCache::remove_script(get_path()); + } } ////////////////////////////// @@ -1826,6 +2020,42 @@ Error GDScriptLanguage::execute_file(const String &p_path) { } void GDScriptLanguage::finish() { + if (_call_stack) { + memdelete_arr(_call_stack); + _call_stack = nullptr; + } + + // Clear the cache before parsing the script_list + GDScriptCache::clear(); + + // Clear dependencies between scripts, to ensure cyclic references are broken + // (to avoid leaks at exit). + SelfList<GDScript> *s = script_list.first(); + while (s) { + // This ensures the current script is not released before we can check + // what's the next one in the list (we can't get the next upfront because we + // don't know if the reference breaking will cause it -or any other after + // it, for that matter- to be released so the next one is not the same as + // before). + Ref<GDScript> scr = s->self(); + if (scr.is_valid()) { + for (KeyValue<StringName, GDScriptFunction *> &E : scr->member_functions) { + GDScriptFunction *func = E.value; + for (int i = 0; i < func->argument_types.size(); i++) { + func->argument_types.write[i].script_type_ref = Ref<Script>(); + } + func->return_type.script_type_ref = Ref<Script>(); + } + for (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) { + E.value.data_type.script_type_ref = Ref<Script>(); + } + + // Clear backup for scripts that could slip out of the cyclic reference + // check + scr->clear(); + } + s = s->next(); + } } void GDScriptLanguage::profiling_start() { @@ -1935,7 +2165,8 @@ void GDScriptLanguage::reload_all_scripts() { SelfList<GDScript> *elem = script_list.first(); while (elem) { - if (elem->self()->get_path().is_resource_file()) { + // Scripts will reload all subclasses, so only reload root scripts. + if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) { print_verbose("GDScript: Found: " + elem->self()->get_path()); scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident } @@ -1964,7 +2195,8 @@ void GDScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_so SelfList<GDScript> *elem = script_list.first(); while (elem) { - if (elem->self()->get_path().is_resource_file()) { + // Scripts will reload all subclasses, so only reload root scripts. + if (elem->self()->is_root_script() && elem->self()->get_path().is_resource_file()) { scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident } elem = elem->next(); @@ -2337,35 +2569,6 @@ GDScriptLanguage::GDScriptLanguage() { } GDScriptLanguage::~GDScriptLanguage() { - if (_call_stack) { - memdelete_arr(_call_stack); - } - - // Clear dependencies between scripts, to ensure cyclic references are broken (to avoid leaks at exit). - SelfList<GDScript> *s = script_list.first(); - while (s) { - GDScript *scr = s->self(); - // This ensures the current script is not released before we can check what's the next one - // in the list (we can't get the next upfront because we don't know if the reference breaking - // will cause it -or any other after it, for that matter- to be released so the next one - // is not the same as before). - scr->reference(); - - for (KeyValue<StringName, GDScriptFunction *> &E : scr->member_functions) { - GDScriptFunction *func = E.value; - for (int i = 0; i < func->argument_types.size(); i++) { - func->argument_types.write[i].script_type_ref = Ref<Script>(); - } - func->return_type.script_type_ref = Ref<Script>(); - } - for (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) { - E.value.data_type.script_type_ref = Ref<Script>(); - } - - s = s->next(); - scr->unreference(); - } - singleton = nullptr; } @@ -2387,6 +2590,27 @@ Ref<GDScript> GDScriptLanguage::get_orphan_subclass(const String &p_qualified_na return Ref<GDScript>(Object::cast_to<GDScript>(obj)); } +Ref<GDScript> GDScriptLanguage::get_script_by_fully_qualified_name(const String &p_name) { + { + MutexLock lock(mutex); + + SelfList<GDScript> *elem = script_list.first(); + while (elem) { + GDScript *scr = elem->self(); + scr = scr->find_class(p_name); + if (scr != nullptr) { + return scr; + } + elem = elem->next(); + } + } + + Ref<GDScript> scr; + scr.instantiate(); + scr->fully_qualified_name = p_name; + return scr; +} + /*************** RESOURCE ***************/ Ref<Resource> ResourceFormatLoaderGDScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 72ad890fbc..7911ea47ec 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -61,6 +61,8 @@ class GDScript : public Script { GDCLASS(GDScript, Script); bool tool = false; bool valid = false; + bool reloading = false; + bool skip_dependencies = false; struct MemberInfo { int index = 0; @@ -124,6 +126,8 @@ class GDScript : public Script { int subclass_count = 0; RBSet<Object *> instances; + bool destructing = false; + bool clearing = false; //exported members String source; String path; @@ -137,7 +141,6 @@ class GDScript : public Script { void _super_implicit_constructor(GDScript *p_script, GDScriptInstance *p_instance, Callable::CallError &r_error); GDScriptInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error); - void _set_subclass_path(Ref<GDScript> &p_sc, const String &p_path); String _get_debug_path() const; #ifdef TOOLS_ENABLED @@ -164,6 +167,9 @@ class GDScript : public Script { // This method will map the class name from "RefCounted" to "MyClass.InnerClass". static String _get_gdscript_reference_class_name(const GDScript *p_gdscript); + GDScript *_get_gdscript_from_variant(const Variant &p_variant); + void _get_dependencies(RBSet<GDScript *> &p_dependencies, const GDScript *p_except); + protected: bool _get(const StringName &p_name, Variant &r_ret) const; bool _set(const StringName &p_name, const Variant &p_value); @@ -174,10 +180,17 @@ protected: static void _bind_methods(); public: + void clear(); + virtual bool is_valid() const override { return valid; } bool inherits_script(const Ref<Script> &p_script) const override; + GDScript *find_class(const String &p_qualified_name); + bool is_subclass(const GDScript *p_script); + GDScript *get_root_script(); + bool is_root_script() const { return _owner == nullptr; } + String get_fully_qualified_name() const { return fully_qualified_name; } const HashMap<StringName, Ref<GDScript>> &get_subclasses() const { return subclasses; } const HashMap<StringName, Variant> &get_constants() const { return constants; } const HashSet<StringName> &get_members() const { return members; } @@ -189,6 +202,10 @@ public: const Ref<GDScriptNativeClass> &get_native() const { return native; } const String &get_script_class_name() const { return name; } + RBSet<GDScript *> get_dependencies(); + RBSet<GDScript *> get_inverted_dependencies(); + RBSet<GDScript *> get_must_clear_dependencies(); + virtual bool has_script_signal(const StringName &p_signal) const override; virtual void get_script_signal_list(List<MethodInfo> *r_signals) const override; @@ -223,7 +240,7 @@ public: virtual Error reload(bool p_keep_state = false) override; virtual void set_path(const String &p_path, bool p_take_over = false) override; - void set_script_path(const String &p_path) { path = p_path; } //because subclasses need a path too... + String get_script_path() const; Error load_source_code(const String &p_path); Error load_byte_code(const String &p_path); @@ -267,6 +284,7 @@ class GDScriptInstance : public ScriptInstance { friend class GDScriptLambdaCallable; friend class GDScriptLambdaSelfCallable; friend class GDScriptCompiler; + friend class GDScriptCache; friend struct GDScriptUtilityFunctionsDefinitions; ObjectID owner_id; @@ -415,7 +433,7 @@ public: csi.write[_debug_call_stack_pos - i - 1].line = _call_stack[i].line ? *_call_stack[i].line : 0; if (_call_stack[i].function) { csi.write[_debug_call_stack_pos - i - 1].func = _call_stack[i].function->get_name(); - csi.write[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_path(); + csi.write[_debug_call_stack_pos - i - 1].file = _call_stack[i].function->get_script()->get_script_path(); } } return csi; @@ -515,6 +533,8 @@ public: void add_orphan_subclass(const String &p_qualified_name, const ObjectID &p_subclass); Ref<GDScript> get_orphan_subclass(const String &p_qualified_name); + Ref<GDScript> get_script_by_fully_qualified_name(const String &p_name); + GDScriptLanguage(); ~GDScriptLanguage(); }; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 704dda8045..81e50d6b79 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -32,6 +32,7 @@ #include "core/config/engine.h" #include "core/config/project_settings.h" +#include "core/core_string_names.h" #include "core/io/file_access.h" #include "core/io/resource_loader.h" #include "core/object/class_db.h" @@ -39,6 +40,7 @@ #include "core/templates/hash_map.h" #include "gdscript.h" #include "gdscript_utility_functions.h" +#include "scene/resources/packed_scene.h" static MethodInfo info_from_utility_func(const StringName &p_function) { ERR_FAIL_COND_V(!Variant::has_utility_function(p_function), MethodInfo()); @@ -132,7 +134,7 @@ static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) { return type; } -bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class) { +bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_member) { if (p_class->members_indices.has(p_member_name)) { int index = p_class->members_indices[p_member_name]; const GDScriptParser::ClassNode::Member *member = &p_class->members[index]; @@ -145,6 +147,9 @@ bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName member->type == GDScriptParser::ClassNode::Member::SIGNAL) { return true; } + if (p_member->type != GDScriptParser::Node::FUNCTION && member->type == GDScriptParser::ClassNode::Member::FUNCTION) { + return true; + } } return false; @@ -160,6 +165,9 @@ bool GDScriptAnalyzer::has_member_name_conflict_in_native_type(const StringName if (ClassDB::has_integer_constant(p_native_type_string, p_member_name)) { return true; } + if (p_member_name == CoreStringNames::get_singleton()->_script) { + return true; + } return false; } @@ -187,14 +195,18 @@ Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::C const GDScriptParser::DataType *current_data_type = &p_class_node->base_type; while (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::CLASS) { GDScriptParser::ClassNode *current_class_node = current_data_type->class_type; - if (has_member_name_conflict_in_script_class(p_member_name, current_class_node)) { - push_error(vformat(R"(The member "%s" already exists in a parent class.)", p_member_name), - p_member_node); + if (has_member_name_conflict_in_script_class(p_member_name, current_class_node, p_member_node)) { + String parent_class_name = current_class_node->fqcn; + if (current_class_node->identifier != nullptr) { + parent_class_name = current_class_node->identifier->name; + } + push_error(vformat(R"(The member "%s" already exists in parent class %s.)", p_member_name, parent_class_name), p_member_node); return ERR_PARSE_ERROR; } current_data_type = ¤t_class_node->base_type; } + // No need for native class recursion because Node exposes all Object's properties. if (current_data_type && current_data_type->kind == GDScriptParser::DataType::Kind::NATIVE) { if (current_data_type->native_type != StringName()) { return check_native_member_name_conflict( @@ -207,22 +219,28 @@ Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::C return OK; } +void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list) { + if (p_list->find(p_node) != nullptr) { + return; + } + p_list->push_back(p_node); + + // Prioritize node base type over its outer class + if (p_node->base_type.class_type != nullptr) { + get_class_node_current_scope_classes(p_node->base_type.class_type, p_list); + } + + if (p_node->outer != nullptr) { + get_class_node_current_scope_classes(p_node->outer, p_list); + } +} + Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive) { if (p_class->base_type.is_set()) { // Already resolved return OK; } - if (p_class == parser->head) { - if (p_class->identifier) { - p_class->fqcn = p_class->identifier->name; - } else { - p_class->fqcn = parser->script_path; - } - } else { - p_class->fqcn = p_class->outer->fqcn + "::" + String(p_class->identifier->name); - } - if (p_class->identifier) { StringName class_name = p_class->identifier->name; if (class_exists(class_name)) { @@ -325,9 +343,10 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, base.native_type = name; } else { // Look for other classes in script. - GDScriptParser::ClassNode *look_class = p_class; bool found = false; - while (look_class != nullptr) { + List<GDScriptParser::ClassNode *> script_classes; + get_class_node_current_scope_classes(p_class, &script_classes); + for (GDScriptParser::ClassNode *look_class : script_classes) { if (look_class->identifier && look_class->identifier->name == name) { if (!look_class->get_datatype().is_set()) { Error err = resolve_inheritance(look_class, false); @@ -351,7 +370,6 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, found = true; break; } - look_class = look_class->outer; } if (!found) { @@ -495,8 +513,8 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type result = ref->get_parser()->head->get_datatype(); } else { result.kind = GDScriptParser::DataType::SCRIPT; - result.native_type = ScriptServer::get_global_class_native_base(first); result.script_type = ResourceLoader::load(path, "Script"); + result.native_type = result.script_type->get_instance_base_type(); result.script_path = path; result.is_constant = true; result.is_meta_type = false; @@ -515,12 +533,11 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type result = make_native_enum_type(parser->current_class->base_type.native_type, first); } else { // Classes in current scope. - GDScriptParser::ClassNode *script_class = parser->current_class; - bool found = false; - while (!found && script_class != nullptr) { + List<GDScriptParser::ClassNode *> script_classes; + get_class_node_current_scope_classes(parser->current_class, &script_classes); + for (GDScriptParser::ClassNode *script_class : script_classes) { if (script_class->identifier && script_class->identifier->name == first) { result = script_class->get_datatype(); - found = true; break; } if (script_class->members_indices.has(first)) { @@ -528,24 +545,21 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type switch (member.type) { case GDScriptParser::ClassNode::Member::CLASS: result = member.m_class->get_datatype(); - found = true; break; case GDScriptParser::ClassNode::Member::ENUM: result = member.m_enum->get_datatype(); - found = true; break; case GDScriptParser::ClassNode::Member::CONSTANT: if (member.constant->get_datatype().is_meta_type) { result = member.constant->get_datatype(); result.is_meta_type = false; - found = true; break; } else if (Ref<Script>(member.constant->initializer->reduced_value).is_valid()) { Ref<GDScript> gdscript = member.constant->initializer->reduced_value; if (gdscript.is_valid()) { - Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_path()); + Ref<GDScriptParserRef> ref = get_parser_for(gdscript->get_script_path()); if (ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED) != OK) { - push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_path()), p_type); + push_error(vformat(R"(Could not parse script from "%s".)", gdscript->get_script_path()), p_type); return GDScriptParser::DataType(); } result = ref->get_parser()->head->get_datatype(); @@ -567,7 +581,6 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return GDScriptParser::DataType(); } } - script_class = script_class->outer; } } if (!result.is_set()) { @@ -1163,6 +1176,8 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * if (p_function->parameters[i]->default_value->is_constant) { p_function->default_arg_values.push_back(p_function->parameters[i]->default_value->reduced_value); + } else { + p_function->default_arg_values.push_back(Variant()); // Prevent shift. } } #endif // TOOLS_ENABLED @@ -1215,11 +1230,7 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * if (!valid) { // Compute parent signature as a string to show in the error message. - String parent_signature = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant"; - if (parent_signature == "null") { - parent_signature = "void"; - } - parent_signature += " " + p_function->identifier->name.operator String() + "("; + String parent_signature = p_function->identifier->name.operator String() + "("; int j = 0; for (const GDScriptParser::DataType &par_type : parameters_types) { if (j > 0) { @@ -1236,7 +1247,15 @@ void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode * j++; } - parent_signature += ")"; + parent_signature += ") -> "; + + const String return_type = parent_return_type.is_hard_type() ? parent_return_type.to_string() : "Variant"; + if (return_type == "null") { + parent_signature += "void"; + } else { + parent_signature += return_type; + } + push_error(vformat(R"(The function signature doesn't match the parent. Parent signature is "%s".)", parent_signature), p_function); } } @@ -1726,7 +1745,6 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame } else { result.type_source = GDScriptParser::DataType::INFERRED; } - result.is_constant = false; } if (p_parameter->datatype_specifier != nullptr) { @@ -1746,6 +1764,7 @@ void GDScriptAnalyzer::resolve_parameter(GDScriptParser::ParameterNode *p_parame push_error(vformat(R"(Could not infer the type of the variable "%s" because the initial value is "null".)", p_parameter->identifier->name), p_parameter->default_value); } + result.is_constant = false; p_parameter->set_datatype(result); } @@ -2152,7 +2171,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o GDScriptParser::DataType test_type = right_type; test_type.is_meta_type = false; - if (!is_type_compatible(test_type, p_binary_op->left_operand->get_datatype(), false)) { + if (!is_type_compatible(test_type, left_type, false)) { push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)"), p_binary_op->left_operand); p_binary_op->reduced_value = false; } else { @@ -2186,11 +2205,11 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o GDScriptParser::DataType test_type = right_type; test_type.is_meta_type = false; - if (!is_type_compatible(test_type, p_binary_op->left_operand->get_datatype(), false)) { + if (!is_type_compatible(test_type, left_type, false)) { // Test reverse as well to consider for subtypes. - if (!is_type_compatible(p_binary_op->left_operand->get_datatype(), test_type, false)) { - if (p_binary_op->left_operand->get_datatype().is_hard_type()) { - push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", p_binary_op->left_operand->get_datatype().to_string(), test_type.to_string()), p_binary_op->left_operand); + if (!is_type_compatible(left_type, test_type, false)) { + if (left_type.is_hard_type()) { + push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", left_type.to_string(), test_type.to_string()), p_binary_op->left_operand); } else { // TODO: Warning. mark_node_unsafe(p_binary_op); @@ -2564,8 +2583,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a parser->push_warning(p_call, GDScriptWarning::RETURN_VALUE_DISCARDED, p_call->function_name); } - if (is_static && !base_type.is_meta_type && !(callee_type != GDScriptParser::Node::SUBSCRIPT && parser->current_function != nullptr && parser->current_function->is_static)) { - parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, base_type.to_string()); + if (is_static && !base_type.is_meta_type && !(is_self && parser->current_function != nullptr && parser->current_function->is_static)) { + String caller_type = String(base_type.native_type); + + if (caller_type.is_empty()) { + caller_type = base_type.to_string(); + } + + parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, caller_type); } #endif // DEBUG_ENABLED @@ -2662,7 +2687,7 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { } void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) { - HashMap<Variant, GDScriptParser::ExpressionNode *, VariantHasher, VariantComparator> elements; + HashMap<Variant, GDScriptParser::ExpressionNode *, VariantHasher, StringLikeVariantComparator> elements; for (int i = 0; i < p_dictionary->elements.size(); i++) { const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; @@ -2728,21 +2753,13 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str return type; } - type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - type.kind = GDScriptParser::DataType::CLASS; - type.builtin_type = Variant::OBJECT; - type.native_type = ScriptServer::get_global_class_native_base(p_class_name); - type.class_type = ref->get_parser()->head; - type.script_path = ref->get_parser()->script_path; - type.is_constant = true; - type.is_meta_type = true; - return type; + return ref->get_parser()->head->get_datatype(); } else { type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; type.kind = GDScriptParser::DataType::SCRIPT; type.builtin_type = Variant::OBJECT; - type.native_type = ScriptServer::get_global_class_native_base(p_class_name); type.script_type = ResourceLoader::load(path, "Script"); + type.native_type = type.script_type->get_instance_base_type(); type.script_path = path; type.is_constant = true; type.is_meta_type = true; @@ -2866,6 +2883,9 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod p_identifier->variable_source = member.variable; member.variable->usages += 1; break; + case GDScriptParser::ClassNode::Member::SIGNAL: + p_identifier->source = GDScriptParser::IdentifierNode::MEMBER_SIGNAL; + break; case GDScriptParser::ClassNode::Member::FUNCTION: resolve_function_signature(member.function); p_identifier->set_datatype(make_callable_type(member.function->info)); @@ -2882,47 +2902,49 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod } // Check outer constants. // TODO: Allow outer static functions. - GDScriptParser::ClassNode *outer = base_class->outer; - while (outer != nullptr) { - if (outer->has_member(name)) { - const GDScriptParser::ClassNode::Member &member = outer->get_member(name); - switch (member.type) { - case GDScriptParser::ClassNode::Member::CONSTANT: { - // TODO: Make sure loops won't cause problem. And make special error message for those. - // For out-of-order resolution: - reduce_expression(member.constant->initializer); - p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = true; - p_identifier->reduced_value = member.constant->initializer->reduced_value; - return; - } break; - case GDScriptParser::ClassNode::Member::ENUM_VALUE: { - p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = true; - p_identifier->reduced_value = member.enum_value.value; - return; - } break; - case GDScriptParser::ClassNode::Member::ENUM: { - p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = false; - return; - } break; - case GDScriptParser::ClassNode::Member::CLASS: { - resolve_class_interface(member.m_class); - p_identifier->set_datatype(member.m_class->get_datatype()); - return; - } break; - default: - break; + if (base_class->outer != nullptr) { + List<GDScriptParser::ClassNode *> script_classes; + get_class_node_current_scope_classes(parser->current_class, &script_classes); + for (GDScriptParser::ClassNode *script_class : script_classes) { + if (script_class->has_member(name)) { + const GDScriptParser::ClassNode::Member &member = script_class->get_member(name); + switch (member.type) { + case GDScriptParser::ClassNode::Member::CONSTANT: { + // TODO: Make sure loops won't cause problem. And make special error message for those. + // For out-of-order resolution: + reduce_expression(member.constant->initializer); + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.constant->initializer->reduced_value; + return; + } break; + case GDScriptParser::ClassNode::Member::ENUM_VALUE: { + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.enum_value.value; + return; + } break; + case GDScriptParser::ClassNode::Member::ENUM: { + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = false; + return; + } break; + case GDScriptParser::ClassNode::Member::CLASS: { + resolve_class_interface(member.m_class); + p_identifier->set_datatype(member.m_class->get_datatype()); + return; + } break; + default: + break; + } } } - outer = outer->outer; } base_class = base_class->base_type.class_type; } - // Check native members. + // Check native members. No need for native class recursion because Node exposes all Object's properties. const StringName &native = base.native_type; if (class_exists(native)) { @@ -3010,6 +3032,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident p_identifier->reduced_value = p_identifier->constant_source->initializer->reduced_value; found_source = true; break; + case GDScriptParser::IdentifierNode::MEMBER_SIGNAL: case GDScriptParser::IdentifierNode::INHERITED_VARIABLE: mark_lambda_use_self(); break; @@ -3113,7 +3136,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident GDScriptParser::DataType result; result.kind = GDScriptParser::DataType::NATIVE; result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - if (autoload.path.to_lower().ends_with(GDScriptLanguage::get_singleton()->get_extension())) { + if (ResourceLoader::get_resource_type(autoload.path) == "GDScript") { Ref<GDScriptParserRef> singl_parser = get_parser_for(autoload.path); if (singl_parser.is_valid()) { Error err = singl_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); @@ -3121,6 +3144,23 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident result = type_from_metatype(singl_parser->get_parser()->head->get_datatype()); } } + } else if (ResourceLoader::get_resource_type(autoload.path) == "PackedScene") { + if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(name)) { + Variant constant = GDScriptLanguage::get_singleton()->get_named_globals_map()[name]; + Node *node = Object::cast_to<Node>(constant); + if (node != nullptr) { + Ref<GDScript> scr = node->get_script(); + if (scr.is_valid()) { + Ref<GDScriptParserRef> singl_parser = get_parser_for(scr->get_script_path()); + if (singl_parser.is_valid()) { + Error err = singl_parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + if (err == OK) { + result = type_from_metatype(singl_parser->get_parser()->head->get_datatype()); + } + } + } + } + } } result.is_constant = true; p_identifier->set_datatype(result); @@ -3246,9 +3286,28 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { } } else { // TODO: Don't load if validating: use completion cache. - p_preload->resource = ResourceLoader::load(p_preload->resolved_path); - if (p_preload->resource.is_null()) { - push_error(vformat(R"(Could not preload resource file "%s".)", p_preload->resolved_path), p_preload->path); + + // Must load GDScript and PackedScenes separately to permit cyclic references + // as ResourceLoader::load() detect and reject those. + if (ResourceLoader::get_resource_type(p_preload->resolved_path) == "GDScript") { + Error err = OK; + Ref<GDScript> res = GDScriptCache::get_shallow_script(p_preload->resolved_path, err, parser->script_path); + p_preload->resource = res; + if (err != OK) { + push_error(vformat(R"(Could not preload resource script "%s".)", p_preload->resolved_path), p_preload->path); + } + } else if (ResourceLoader::get_resource_type(p_preload->resolved_path) == "PackedScene") { + Error err = OK; + Ref<PackedScene> res = GDScriptCache::get_packed_scene(p_preload->resolved_path, err, parser->script_path); + p_preload->resource = res; + if (err != OK) { + push_error(vformat(R"(Could not preload resource scene "%s".)", p_preload->resolved_path), p_preload->path); + } + } else { + p_preload->resource = ResourceLoader::load(p_preload->resolved_path); + if (p_preload->resource.is_null()) { + push_error(vformat(R"(Could not preload resource file "%s".)", p_preload->resolved_path), p_preload->path); + } } } } @@ -3286,10 +3345,24 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri if (p_subscript->attribute == nullptr) { return; } - if (p_subscript->base->is_constant) { + + GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); + // If base is a class metatype, use the analyzer instead. + if (p_subscript->base->is_constant && !(base_type.is_meta_type && base_type.kind == GDScriptParser::DataType::CLASS)) { // Just try to get it. bool valid = false; Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid); + + // If it's a GDScript instance, try to get the full script. Maybe it's not still completely loaded. + Ref<GDScript> gdscr = Ref<GDScript>(p_subscript->base->reduced_value); + if (!valid && gdscr.is_valid()) { + Error err = OK; + GDScriptCache::get_full_script(gdscr->get_script_path(), err); + if (err == OK) { + value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid); + } + } + if (!valid) { push_error(vformat(R"(Cannot get member "%s" from "%s".)", p_subscript->attribute->name, p_subscript->base->reduced_value), p_subscript->index); result_type.kind = GDScriptParser::DataType::VARIANT; @@ -3299,8 +3372,6 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri result_type = type_from_variant(value, p_subscript); } } else { - GDScriptParser::DataType base_type = p_subscript->base->get_datatype(); - if (base_type.is_variant() || !base_type.is_hard_type()) { result_type.kind = GDScriptParser::DataType::VARIANT; mark_node_unsafe(p_subscript); @@ -3375,7 +3446,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::QUATERNION: case Variant::AABB: case Variant::OBJECT: - error = index_type.builtin_type != Variant::STRING; + error = index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME; break; // Expect String or number. case Variant::BASIS: @@ -3389,11 +3460,11 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::TRANSFORM3D: case Variant::PROJECTION: error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::FLOAT && - index_type.builtin_type != Variant::STRING; + index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME; break; // Expect String or int. case Variant::COLOR: - error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING; + error = index_type.builtin_type != Variant::INT && index_type.builtin_type != Variant::STRING && index_type.builtin_type != Variant::STRING_NAME; break; // Don't support indexing, but we will check it later. case Variant::RID: @@ -3569,6 +3640,7 @@ void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternar void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) { reduce_expression(p_unary_op->operand); + GDScriptParser::DataType operand_type = p_unary_op->operand->get_datatype(); GDScriptParser::DataType result; if (p_unary_op->operand == nullptr) { @@ -3581,15 +3653,17 @@ void GDScriptAnalyzer::reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op) p_unary_op->is_constant = true; p_unary_op->reduced_value = Variant::evaluate(p_unary_op->variant_op, p_unary_op->operand->reduced_value, Variant()); result = type_from_variant(p_unary_op->reduced_value, p_unary_op); - } else if (p_unary_op->operand->get_datatype().is_variant()) { + } + + if (operand_type.is_variant()) { result.kind = GDScriptParser::DataType::VARIANT; mark_node_unsafe(p_unary_op); } else { bool valid = false; - result = get_operation_type(p_unary_op->variant_op, p_unary_op->operand->get_datatype(), valid, p_unary_op); + result = get_operation_type(p_unary_op->variant_op, operand_type, valid, p_unary_op); if (!valid) { - push_error(vformat(R"(Invalid operand of type "%s" for unary operator "%s".)", p_unary_op->operand->get_datatype().to_string(), Variant::get_operator_name(p_unary_op->variant_op)), p_unary_op->operand); + push_error(vformat(R"(Invalid operand of type "%s" for unary operator "%s".)", operand_type.to_string(), Variant::get_operator_name(p_unary_op->variant_op)), p_unary_op); } } @@ -3657,7 +3731,13 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va result.builtin_type = p_value.get_type(); result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; // Constant has explicit type. - if (p_value.get_type() == Variant::OBJECT) { + if (p_value.get_type() == Variant::NIL) { + // A null value is a variant, not void. + result.kind = GDScriptParser::DataType::VARIANT; + } else if (p_value.get_type() == Variant::OBJECT) { + // Object is treated as a native type, not a builtin type. + result.kind = GDScriptParser::DataType::NATIVE; + Object *obj = p_value; if (!obj) { return GDScriptParser::DataType(); @@ -3672,50 +3752,43 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va scr = obj->get_script(); } if (scr.is_valid()) { - if (scr->is_valid()) { - result.script_type = scr; - result.script_path = scr->get_path(); - Ref<GDScript> gds = scr; - if (gds.is_valid()) { - result.kind = GDScriptParser::DataType::CLASS; - // This might be an inner class, so we want to get the parser for the root. - // But still get the inner class from that tree. - GDScript *current = gds.ptr(); - List<StringName> class_chain; - while (current->_owner) { - // Push to front so it's in reverse. - class_chain.push_front(current->name); - current = current->_owner; - } - - Ref<GDScriptParserRef> ref = get_parser_for(current->get_path()); - if (ref.is_null()) { - push_error("Could not find script in path.", p_source); - GDScriptParser::DataType error_type; - error_type.kind = GDScriptParser::DataType::VARIANT; - return error_type; - } - ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); + result.script_type = scr; + result.script_path = scr->get_path(); + Ref<GDScript> gds = scr; + if (gds.is_valid()) { + result.kind = GDScriptParser::DataType::CLASS; + // This might be an inner class, so we want to get the parser for the root. + // But still get the inner class from that tree. + GDScript *current = gds.ptr(); + List<StringName> class_chain; + while (current->_owner) { + // Push to front so it's in reverse. + class_chain.push_front(current->name); + current = current->_owner; + } - GDScriptParser::ClassNode *found = ref->get_parser()->head; + Ref<GDScriptParserRef> ref = get_parser_for(current->get_path()); + if (ref.is_null()) { + push_error("Could not find script in path.", p_source); + GDScriptParser::DataType error_type; + error_type.kind = GDScriptParser::DataType::VARIANT; + return error_type; + } + ref->raise_status(GDScriptParserRef::INTERFACE_SOLVED); - // It should be okay to assume this exists, since we have a complete script already. - for (const StringName &E : class_chain) { - found = found->get_member(E).m_class; - } + GDScriptParser::ClassNode *found = ref->get_parser()->head; - result.class_type = found; - result.script_path = ref->get_parser()->script_path; - } else { - result.kind = GDScriptParser::DataType::SCRIPT; + // It should be okay to assume this exists, since we have a complete script already. + for (const StringName &E : class_chain) { + found = found->get_member(E).m_class; } - result.native_type = scr->get_instance_base_type(); + + result.class_type = found; + result.script_path = ref->get_parser()->script_path; } else { - push_error(vformat(R"(Constant value uses script from "%s" which is loaded but not compiled.)", scr->get_path()), p_source); - result.kind = GDScriptParser::DataType::VARIANT; - result.type_source = GDScriptParser::DataType::UNDETECTED; - result.is_meta_type = false; + result.kind = GDScriptParser::DataType::SCRIPT; } + result.native_type = scr->get_instance_base_type(); } else { result.kind = GDScriptParser::DataType::NATIVE; if (result.native_type == GDScriptNativeClass::get_class_static()) { @@ -4114,6 +4187,8 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ if (p_target.kind == GDScriptParser::DataType::BUILTIN) { bool valid = p_source.kind == GDScriptParser::DataType::BUILTIN && p_target.builtin_type == p_source.builtin_type; + valid |= p_source.builtin_type == Variant::STRING && p_target.builtin_type == Variant::STRING_NAME; + valid |= p_source.builtin_type == Variant::STRING_NAME && p_target.builtin_type == Variant::STRING; if (!valid && p_allow_implicit_conversion) { valid = Variant::can_convert_strict(p_source.builtin_type, p_target.builtin_type); } diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 217a856ce0..44ca1593ed 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -45,11 +45,13 @@ class GDScriptAnalyzer { List<GDScriptParser::LambdaNode *> lambda_stack; // Tests for detecting invalid overloading of script members - static _FORCE_INLINE_ bool has_member_name_conflict_in_script_class(const StringName &p_name, const GDScriptParser::ClassNode *p_current_class_node); + static _FORCE_INLINE_ bool has_member_name_conflict_in_script_class(const StringName &p_name, const GDScriptParser::ClassNode *p_current_class_node, const GDScriptParser::Node *p_member); static _FORCE_INLINE_ bool has_member_name_conflict_in_native_type(const StringName &p_name, const StringName &p_native_type_string); Error check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string); Error check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node); + void get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list); + Error resolve_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive = true); GDScriptParser::DataType resolve_datatype(GDScriptParser::TypeNode *p_type); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index fa158591fd..1bc83fbbb5 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -164,7 +164,7 @@ void GDScriptByteCodeGenerator::write_start(GDScript *p_script, const StringName function->name = p_function_name; function->_script = p_script; - function->source = p_script->get_path(); + function->source = p_script->get_script_path(); #ifdef DEBUG_ENABLED function->func_cname = (String(function->source) + " - " + String(p_function_name)).utf8(); diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 271296c2f9..d1467eea95 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -34,7 +34,9 @@ #include "core/templates/vector.h" #include "gdscript.h" #include "gdscript_analyzer.h" +#include "gdscript_compiler.h" #include "gdscript_parser.h" +#include "scene/resources/packed_scene.h" bool GDScriptParserRef::is_valid() const { return parser != nullptr; @@ -95,27 +97,96 @@ Error GDScriptParserRef::raise_status(Status p_new_status) { return result; } -GDScriptParserRef::~GDScriptParserRef() { +void GDScriptParserRef::clear() { + if (cleared) { + return; + } + cleared = true; + if (parser != nullptr) { memdelete(parser); } + if (analyzer != nullptr) { memdelete(analyzer); } - MutexLock lock(GDScriptCache::singleton->lock); +} + +GDScriptParserRef::~GDScriptParserRef() { + clear(); + + MutexLock lock(GDScriptCache::singleton->mutex); GDScriptCache::singleton->parser_map.erase(path); } GDScriptCache *GDScriptCache::singleton = nullptr; +void GDScriptCache::move_script(const String &p_from, const String &p_to) { + if (singleton == nullptr || p_from == p_to) { + return; + } + + MutexLock lock(singleton->mutex); + + if (singleton->cleared) { + return; + } + + for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) { + if (E.value.has(p_from)) { + E.value.insert(p_to); + E.value.erase(p_from); + } + } + + if (singleton->parser_map.has(p_from) && !p_from.is_empty()) { + singleton->parser_map[p_to] = singleton->parser_map[p_from]; + } + singleton->parser_map.erase(p_from); + + if (singleton->shallow_gdscript_cache.has(p_from) && !p_from.is_empty()) { + singleton->shallow_gdscript_cache[p_to] = singleton->shallow_gdscript_cache[p_from]; + } + singleton->shallow_gdscript_cache.erase(p_from); + + if (singleton->full_gdscript_cache.has(p_from) && !p_from.is_empty()) { + singleton->full_gdscript_cache[p_to] = singleton->full_gdscript_cache[p_from]; + } + singleton->full_gdscript_cache.erase(p_from); +} + void GDScriptCache::remove_script(const String &p_path) { - MutexLock lock(singleton->lock); + if (singleton == nullptr) { + return; + } + + MutexLock lock(singleton->mutex); + + if (singleton->cleared) { + return; + } + + for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) { + if (!E.value.has(p_path)) { + continue; + } + E.value.erase(p_path); + } + + GDScriptCache::clear_unreferenced_packed_scenes(); + + if (singleton->parser_map.has(p_path)) { + singleton->parser_map[p_path]->clear(); + singleton->parser_map.erase(p_path); + } + + singleton->dependencies.erase(p_path); singleton->shallow_gdscript_cache.erase(p_path); singleton->full_gdscript_cache.erase(p_path); } Ref<GDScriptParserRef> GDScriptCache::get_parser(const String &p_path, GDScriptParserRef::Status p_status, Error &r_error, const String &p_owner) { - MutexLock lock(singleton->lock); + MutexLock lock(singleton->mutex); Ref<GDScriptParserRef> ref; if (!p_owner.is_empty()) { singleton->dependencies[p_owner].insert(p_path); @@ -161,8 +232,8 @@ String GDScriptCache::get_source_code(const String &p_path) { return source; } -Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const String &p_owner) { - MutexLock lock(singleton->lock); +Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_error, const String &p_owner) { + MutexLock lock(singleton->mutex); if (!p_owner.is_empty()) { singleton->dependencies[p_owner].insert(p_path); } @@ -176,15 +247,19 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, const Stri Ref<GDScript> script; script.instantiate(); script->set_path(p_path, true); - script->set_script_path(p_path); script->load_source_code(p_path); - singleton->shallow_gdscript_cache[p_path] = script.ptr(); + Ref<GDScriptParserRef> parser_ref = get_parser(p_path, GDScriptParserRef::PARSED, r_error); + if (r_error == OK) { + GDScriptCompiler::make_scripts(script.ptr(), parser_ref->get_parser()->get_tree(), true); + } + + singleton->shallow_gdscript_cache[p_path] = script; return script; } Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_error, const String &p_owner, bool p_update_from_disk) { - MutexLock lock(singleton->lock); + MutexLock lock(singleton->mutex); if (!p_owner.is_empty()) { singleton->dependencies[p_owner].insert(p_path); @@ -193,38 +268,60 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro Ref<GDScript> script; r_error = OK; if (singleton->full_gdscript_cache.has(p_path)) { - script = Ref<GDScript>(singleton->full_gdscript_cache[p_path]); + script = singleton->full_gdscript_cache[p_path]; if (!p_update_from_disk) { return script; } } if (script.is_null()) { - script = get_shallow_script(p_path); - ERR_FAIL_COND_V(script.is_null(), Ref<GDScript>()); + script = get_shallow_script(p_path, r_error); + if (r_error) { + return script; + } } - r_error = script->load_source_code(p_path); + if (p_update_from_disk) { + r_error = script->load_source_code(p_path); + } if (r_error) { return script; } - r_error = script->reload(); + singleton->full_gdscript_cache[p_path] = script; + singleton->shallow_gdscript_cache.erase(p_path); + + r_error = script->reload(true); if (r_error) { + singleton->shallow_gdscript_cache[p_path] = script; + singleton->full_gdscript_cache.erase(p_path); return script; } - singleton->full_gdscript_cache[p_path] = script.ptr(); - singleton->shallow_gdscript_cache.erase(p_path); - return script; } +Ref<GDScript> GDScriptCache::get_cached_script(const String &p_path) { + MutexLock lock(singleton->mutex); + + if (singleton->full_gdscript_cache.has(p_path)) { + return singleton->full_gdscript_cache[p_path]; + } + + if (singleton->shallow_gdscript_cache.has(p_path)) { + return singleton->shallow_gdscript_cache[p_path]; + } + + return Ref<GDScript>(); +} + Error GDScriptCache::finish_compiling(const String &p_owner) { + MutexLock lock(singleton->mutex); + // Mark this as compiled. - Ref<GDScript> script = get_shallow_script(p_owner); - singleton->full_gdscript_cache[p_owner] = script.ptr(); + Ref<GDScript> script = get_cached_script(p_owner); + singleton->full_gdscript_cache[p_owner] = script; singleton->shallow_gdscript_cache.erase(p_owner); HashSet<String> depends = singleton->dependencies[p_owner]; @@ -245,13 +342,98 @@ Error GDScriptCache::finish_compiling(const String &p_owner) { return err; } +Ref<PackedScene> GDScriptCache::get_packed_scene(const String &p_path, Error &r_error, const String &p_owner) { + MutexLock lock(singleton->mutex); + + if (singleton->packed_scene_cache.has(p_path)) { + singleton->packed_scene_dependencies[p_path].insert(p_owner); + return singleton->packed_scene_cache[p_path]; + } + + Ref<PackedScene> scene = ResourceCache::get_ref(p_path); + if (scene.is_valid()) { + singleton->packed_scene_cache[p_path] = scene; + singleton->packed_scene_dependencies[p_path].insert(p_owner); + return scene; + } + scene.instantiate(); + + r_error = OK; + if (p_path.is_empty()) { + r_error = ERR_FILE_BAD_PATH; + return scene; + } + + scene->set_path(p_path); + singleton->packed_scene_cache[p_path] = scene; + singleton->packed_scene_dependencies[p_path].insert(p_owner); + + scene->reload_from_file(); + return scene; +} + +void GDScriptCache::clear_unreferenced_packed_scenes() { + if (singleton == nullptr) { + return; + } + + MutexLock lock(singleton->mutex); + + if (singleton->cleared) { + return; + } + + for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) { + if (E.value.size() > 0 || !ResourceLoader::is_imported(E.key)) { + continue; + } + + singleton->packed_scene_dependencies.erase(E.key); + singleton->packed_scene_cache.erase(E.key); + } +} + +void GDScriptCache::clear() { + if (singleton == nullptr) { + return; + } + + MutexLock lock(singleton->mutex); + + if (singleton->cleared) { + return; + } + singleton->cleared = true; + + RBSet<Ref<GDScriptParserRef>> parser_map_refs; + for (KeyValue<String, GDScriptParserRef *> &E : singleton->parser_map) { + parser_map_refs.insert(E.value); + } + + for (Ref<GDScriptParserRef> &E : parser_map_refs) { + if (E.is_valid()) + E->clear(); + } + + singleton->packed_scene_dependencies.clear(); + singleton->packed_scene_cache.clear(); + + parser_map_refs.clear(); + singleton->parser_map.clear(); + singleton->shallow_gdscript_cache.clear(); + singleton->full_gdscript_cache.clear(); + + singleton->packed_scene_cache.clear(); + singleton->packed_scene_dependencies.clear(); +} + GDScriptCache::GDScriptCache() { singleton = this; } GDScriptCache::~GDScriptCache() { - parser_map.clear(); - shallow_gdscript_cache.clear(); - full_gdscript_cache.clear(); + if (!cleared) { + clear(); + } singleton = nullptr; } diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index 3d111ea229..0ee269f96c 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -36,6 +36,7 @@ #include "core/templates/hash_map.h" #include "core/templates/hash_set.h" #include "gdscript.h" +#include "scene/resources/packed_scene.h" class GDScriptAnalyzer; class GDScriptParser; @@ -56,6 +57,7 @@ private: Status status = EMPTY; Error result = OK; String path; + bool cleared = false; friend class GDScriptCache; @@ -64,6 +66,7 @@ public: Status get_status() const; GDScriptParser *get_parser() const; Error raise_status(Status p_new_status); + void clear(); GDScriptParserRef() {} ~GDScriptParserRef(); @@ -72,25 +75,37 @@ public: class GDScriptCache { // String key is full path. HashMap<String, GDScriptParserRef *> parser_map; - HashMap<String, GDScript *> shallow_gdscript_cache; - HashMap<String, GDScript *> full_gdscript_cache; + HashMap<String, Ref<GDScript>> shallow_gdscript_cache; + HashMap<String, Ref<GDScript>> full_gdscript_cache; HashMap<String, HashSet<String>> dependencies; + HashMap<String, Ref<PackedScene>> packed_scene_cache; + HashMap<String, HashSet<String>> packed_scene_dependencies; friend class GDScript; friend class GDScriptParserRef; + friend class GDScriptInstance; static GDScriptCache *singleton; - Mutex lock; - static void remove_script(const String &p_path); + bool cleared = false; + + Mutex mutex; public: + static void move_script(const String &p_from, const String &p_to); + static void remove_script(const String &p_path); static Ref<GDScriptParserRef> get_parser(const String &p_path, GDScriptParserRef::Status status, Error &r_error, const String &p_owner = String()); static String get_source_code(const String &p_path); - static Ref<GDScript> get_shallow_script(const String &p_path, const String &p_owner = String()); + static Ref<GDScript> get_shallow_script(const String &p_path, Error &r_error, const String &p_owner = String()); static Ref<GDScript> get_full_script(const String &p_path, Error &r_error, const String &p_owner = String(), bool p_update_from_disk = false); + static Ref<GDScript> get_cached_script(const String &p_path); static Error finish_compiling(const String &p_owner); + static Ref<PackedScene> get_packed_scene(const String &p_path, Error &r_error, const String &p_owner = ""); + static void clear_unreferenced_packed_scenes(); + + static void clear(); + GDScriptCache(); ~GDScriptCache(); }; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index fd13edc4cf..2a98b856ce 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -43,7 +43,7 @@ bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringN return false; } - if (codegen.parameters.has(p_name) || codegen.locals.has(p_name)) { + if (_is_local_or_parameter(codegen, p_name)) { return false; //shadowed } @@ -65,6 +65,10 @@ bool GDScriptCompiler::_is_class_member_property(GDScript *owner, const StringNa return ClassDB::has_property(nc->get_name(), p_name); } +bool GDScriptCompiler::_is_local_or_parameter(CodeGen &codegen, const StringName &p_name) { + return codegen.parameters.has(p_name) || codegen.locals.has(p_name); +} + void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::Node *p_node) { if (!error.is_empty()) { return; @@ -80,7 +84,7 @@ void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::N } } -GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) const { +GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner) { if (!p_datatype.is_set() || !p_datatype.is_hard_type()) { return GDScriptDataType(); } @@ -103,74 +107,45 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } break; case GDScriptParser::DataType::SCRIPT: { result.kind = GDScriptDataType::SCRIPT; - result.script_type_ref = Ref<Script>(p_datatype.script_type); + result.builtin_type = p_datatype.builtin_type; + result.script_type_ref = p_datatype.script_type; result.script_type = result.script_type_ref.ptr(); - result.native_type = result.script_type->get_instance_base_type(); + result.native_type = p_datatype.native_type; } break; case GDScriptParser::DataType::CLASS: { - // Locate class by constructing the path to it and following that path. - GDScriptParser::ClassNode *class_type = p_datatype.class_type; - if (class_type) { - result.kind = GDScriptDataType::GDSCRIPT; - result.builtin_type = p_datatype.builtin_type; - - String class_name = class_type->fqcn.split("::")[0]; - const bool is_inner_by_path = (!main_script->path.is_empty()) && (class_name == main_script->path); - const bool is_inner_by_name = (!main_script->name.is_empty()) && (class_name == main_script->name); - if (is_inner_by_path || is_inner_by_name) { - // Local class. - List<StringName> names; - while (class_type->outer) { - names.push_back(class_type->identifier->name); - class_type = class_type->outer; - } + result.kind = GDScriptDataType::GDSCRIPT; + result.builtin_type = p_datatype.builtin_type; + result.native_type = p_datatype.native_type; - Ref<GDScript> script = Ref<GDScript>(main_script); - while (names.back()) { - if (!script->subclasses.has(names.back()->get())) { - ERR_PRINT("Parser bug: Cannot locate datatype class."); - result.has_type = false; - return GDScriptDataType(); - } - script = script->subclasses[names.back()->get()]; - names.pop_back(); - } - result.script_type = script.ptr(); - result.native_type = script->get_instance_base_type(); - } else { - // Inner class. - PackedStringArray classes = class_type->fqcn.split("::"); - if (!classes.is_empty()) { - for (GDScript *script : parsed_classes) { - // Checking of inheritance structure of inner class to find a correct script link. - if (script->name == classes[classes.size() - 1]) { - PackedStringArray classes2 = script->fully_qualified_name.split("::"); - bool valid = true; - if (classes.size() != classes2.size()) { - valid = false; - } else { - for (int i = 0; i < classes.size(); i++) { - if (classes[i] != classes2[i]) { - valid = false; - break; - } - } - } - if (!valid) { - continue; - } - result.script_type_ref = Ref<GDScript>(script); - break; - } - } - } - if (result.script_type_ref.is_null()) { - result.script_type_ref = GDScriptCache::get_shallow_script(p_datatype.script_path, main_script->path); - } + String root_name = p_datatype.class_type->fqcn.get_slice("::", 0); + bool is_local_class = !root_name.is_empty() && root_name == main_script->fully_qualified_name; + + Ref<GDScript> script; + if (is_local_class) { + script = Ref<GDScript>(main_script); + } else { + Error err = OK; + script = GDScriptCache::get_shallow_script(p_datatype.script_path, err, p_owner->path); + if (err) { + _set_error(vformat(R"(Could not find script "%s": %s)", p_datatype.script_path, error_names[err]), nullptr); + } + } + + if (script.is_valid()) { + script = Ref<GDScript>(script->find_class(p_datatype.class_type->fqcn)); + } - result.script_type = result.script_type_ref.ptr(); - result.native_type = p_datatype.native_type; + if (script.is_null()) { + _set_error(vformat(R"(Could not find class "%s" in "%s".)", p_datatype.class_type->fqcn, p_datatype.script_path), nullptr); + return GDScriptDataType(); + } else { + // Only hold a strong reference if the owner of the element qualified with this type is not local, to avoid cyclic references (leaks). + // TODO: Might lead to use after free if script_type is a subclass and is used after its parent is freed. + if (!is_local_class) { + result.script_type_ref = script; } + result.script_type = script.ptr(); + result.native_type = p_datatype.native_type; } } break; case GDScriptParser::DataType::ENUM: @@ -189,13 +164,7 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D } if (p_datatype.has_container_element_type()) { - result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type())); - } - - // Only hold strong reference to the script if it's not the owner of the - // element qualified with this type, to avoid cyclic references (leaks). - if (result.script_type && result.script_type == p_owner) { - result.script_type_ref = Ref<Script>(); + result.set_container_element_type(_gdtype_from_datatype(p_datatype.get_container_element_type(), p_owner)); } return result; @@ -367,7 +336,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // This is so one autoload doesn't try to load another before it's compiled. HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); if (autoloads.has(identifier) && autoloads[identifier].is_singleton) { - GDScriptCodeGenerator::Address global = codegen.add_temporary(_gdtype_from_datatype(in->get_datatype())); + GDScriptCodeGenerator::Address global = codegen.add_temporary(_gdtype_from_datatype(in->get_datatype(), codegen.script)); int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; gen->write_store_global(global, idx); return global; @@ -390,11 +359,22 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code if (class_node->identifier && class_node->identifier->name == identifier) { res = Ref<GDScript>(main_script); } else { - res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier)); - if (res.is_null()) { - _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); - r_error = ERR_COMPILATION_FAILED; - return GDScriptCodeGenerator::Address(); + String global_class_path = ScriptServer::get_global_class_path(identifier); + if (ResourceLoader::get_resource_type(global_class_path) == "GDScript") { + Error err = OK; + res = GDScriptCache::get_full_script(global_class_path, err); + if (err != OK) { + _set_error("Can't load global class " + String(identifier), p_expression); + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); + } + } else { + res = ResourceLoader::load(global_class_path); + if (res.is_null()) { + _set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression); + r_error = ERR_COMPILATION_FAILED; + return GDScriptCodeGenerator::Address(); + } } } @@ -434,7 +414,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code Vector<GDScriptCodeGenerator::Address> values; // Create the result temporary first since it's the last to be killed. - GDScriptDataType array_type = _gdtype_from_datatype(an->get_datatype()); + GDScriptDataType array_type = _gdtype_from_datatype(an->get_datatype(), codegen.script); GDScriptCodeGenerator::Address result = codegen.add_temporary(array_type); for (int i = 0; i < an->elements.size(); i++) { @@ -511,7 +491,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::CAST: { const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression); GDScriptParser::DataType og_cast_type = cn->cast_type->get_datatype(); - GDScriptDataType cast_type = _gdtype_from_datatype(og_cast_type); + GDScriptDataType cast_type = _gdtype_from_datatype(og_cast_type, codegen.script); if (og_cast_type.kind == GDScriptParser::DataType::ENUM) { // Enum types are usually treated as dictionaries, but in this case we want to cast to an integer. @@ -534,7 +514,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } break; case GDScriptParser::Node::CALL: { const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_expression); - GDScriptDataType type = _gdtype_from_datatype(call->get_datatype()); + GDScriptDataType type = _gdtype_from_datatype(call->get_datatype(), codegen.script); GDScriptCodeGenerator::Address result = codegen.add_temporary(type); GDScriptCodeGenerator::Address nil = GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::NIL); @@ -670,7 +650,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code Vector<GDScriptCodeGenerator::Address> args; args.push_back(codegen.add_constant(NodePath(get_node->full_path))); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(get_node->get_datatype(), codegen.script)); MethodBind *get_node_method = ClassDB::get_method("Node", "get_node"); gen->write_call_ptrcall(result, GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::SELF), get_node_method, args); @@ -686,7 +666,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::AWAIT: { const GDScriptParser::AwaitNode *await = static_cast<const GDScriptParser::AwaitNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype(), codegen.script)); within_await = true; GDScriptCodeGenerator::Address argument = _parse_expression(codegen, r_error, await->to_await); within_await = false; @@ -705,7 +685,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Indexing operator. case GDScriptParser::Node::SUBSCRIPT: { const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, subscript->base); if (r_error) { @@ -735,7 +715,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Remove result temp as we don't need it. gen->pop_temporary(); // Faster than indexing self (as if no self. had been used). - return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, MI->value.index, _gdtype_from_datatype(subscript->get_datatype())); + return GDScriptCodeGenerator::Address(GDScriptCodeGenerator::Address::MEMBER, MI->value.index, _gdtype_from_datatype(subscript->get_datatype(), codegen.script)); } } @@ -773,7 +753,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::UNARY_OPERATOR: { const GDScriptParser::UnaryOpNode *unary = static_cast<const GDScriptParser::UnaryOpNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(unary->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(unary->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address operand = _parse_expression(codegen, r_error, unary->operand); if (r_error) { @@ -791,7 +771,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::BINARY_OPERATOR: { const GDScriptParser::BinaryOpNode *binary = static_cast<const GDScriptParser::BinaryOpNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(binary->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(binary->get_datatype(), codegen.script)); switch (binary->operation) { case GDScriptParser::BinaryOpNode::OP_LOGIC_AND: { @@ -867,7 +847,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code case GDScriptParser::Node::TERNARY_OPERATOR: { // x IF a ELSE y operator with early out on failure. const GDScriptParser::TernaryOpNode *ternary = static_cast<const GDScriptParser::TernaryOpNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(ternary->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(ternary->get_datatype(), codegen.script)); gen->write_start_ternary(result); @@ -944,7 +924,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code StringName var_name = identifier->name; if (_is_class_member_property(codegen, var_name)) { assign_class_member_property = var_name; - } else if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) { + } else if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) { is_member_property = true; member_property_setter_function = codegen.script->member_indices[var_name].setter; member_property_has_setter = member_property_setter_function != StringName(); @@ -985,7 +965,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code break; } const GDScriptParser::SubscriptNode *subscript_elem = E->get(); - GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript_elem->get_datatype())); + GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript_elem->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address key; StringName name; @@ -1024,8 +1004,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Perform operator if any. if (assignment->operation != GDScriptParser::AssignmentNode::OP_NONE) { - GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype())); - GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype())); + GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype(), codegen.script)); + GDScriptCodeGenerator::Address value = codegen.add_temporary(_gdtype_from_datatype(subscript->get_datatype(), codegen.script)); if (subscript->is_attribute) { gen->write_get_named(value, name, prev_base); } else { @@ -1130,8 +1110,8 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code StringName name = static_cast<GDScriptParser::IdentifierNode *>(assignment->assignee)->name; if (has_operation) { - GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype())); - GDScriptCodeGenerator::Address member = codegen.add_temporary(_gdtype_from_datatype(assignment->assignee->get_datatype())); + GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype(), codegen.script)); + GDScriptCodeGenerator::Address member = codegen.add_temporary(_gdtype_from_datatype(assignment->assignee->get_datatype(), codegen.script)); gen->write_get_member(member, name); gen->write_binary_operator(op_result, assignment->variant_op, member, assigned_value); gen->pop_temporary(); // Pop member temp. @@ -1155,7 +1135,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code bool is_in_setter = false; StringName setter_function; StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name; - if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) { + if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) { is_member = true; setter_function = codegen.script->member_indices[var_name].setter; has_setter = setter_function != StringName(); @@ -1184,7 +1164,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code bool has_operation = assignment->operation != GDScriptParser::AssignmentNode::OP_NONE; if (has_operation) { // Perform operation. - GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype())); + GDScriptCodeGenerator::Address op_result = codegen.add_temporary(_gdtype_from_datatype(assignment->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address og_value = _parse_expression(codegen, r_error, assignment->assignee); gen->write_binary_operator(op_result, assignment->variant_op, og_value, assigned_value); to_assign = op_result; @@ -1196,7 +1176,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code to_assign = assigned_value; } - GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype()); + GDScriptDataType assign_type = _gdtype_from_datatype(assignment->assignee->get_datatype(), codegen.script); if (has_setter && !is_in_setter) { // Call setter. @@ -1226,7 +1206,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } break; case GDScriptParser::Node::LAMBDA: { const GDScriptParser::LambdaNode *lambda = static_cast<const GDScriptParser::LambdaNode *>(p_expression); - GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(lambda->get_datatype())); + GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(lambda->get_datatype(), codegen.script)); Vector<GDScriptCodeGenerator::Address> captures; captures.resize(lambda->captures.size()); @@ -1276,9 +1256,30 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_match_pattern(CodeGen &c equality_type.kind = GDScriptDataType::BUILTIN; equality_type.builtin_type = Variant::BOOL; + GDScriptCodeGenerator::Address type_string_addr = codegen.add_constant(Variant::STRING); + GDScriptCodeGenerator::Address type_string_name_addr = codegen.add_constant(Variant::STRING_NAME); + // Check type equality. GDScriptCodeGenerator::Address type_equality_addr = codegen.add_temporary(equality_type); codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_EQUAL, p_type_addr, literal_type_addr); + + // Check if StringName <-> String comparison is possible. + GDScriptCodeGenerator::Address type_comp_addr_1 = codegen.add_temporary(equality_type); + GDScriptCodeGenerator::Address type_comp_addr_2 = codegen.add_temporary(equality_type); + + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_EQUAL, p_type_addr, type_string_addr); + codegen.generator->write_binary_operator(type_comp_addr_2, Variant::OP_EQUAL, literal_type_addr, type_string_name_addr); + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_AND, type_comp_addr_1, type_comp_addr_2); + codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_OR, type_equality_addr, type_comp_addr_1); + + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_EQUAL, p_type_addr, type_string_name_addr); + codegen.generator->write_binary_operator(type_comp_addr_2, Variant::OP_EQUAL, literal_type_addr, type_string_addr); + codegen.generator->write_binary_operator(type_comp_addr_1, Variant::OP_AND, type_comp_addr_1, type_comp_addr_2); + codegen.generator->write_binary_operator(type_equality_addr, Variant::OP_OR, type_equality_addr, type_comp_addr_1); + + codegen.generator->pop_temporary(); // Remove type_comp_addr_2 from stack. + codegen.generator->pop_temporary(); // Remove type_comp_addr_1 from stack. + codegen.generator->write_and_left_operand(type_equality_addr); // Get literal. @@ -1645,7 +1646,7 @@ void GDScriptCompiler::_add_locals_in_block(CodeGen &codegen, const GDScriptPars // Parameters are added directly from function and loop variables are declared explicitly. continue; } - codegen.add_local(p_block->locals[i].name, _gdtype_from_datatype(p_block->locals[i].get_datatype())); + codegen.add_local(p_block->locals[i].name, _gdtype_from_datatype(p_block->locals[i].get_datatype(), codegen.script)); } } @@ -1675,7 +1676,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui codegen.start_block(); // Evaluate the match expression. - GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype())); + GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address value_expr = _parse_expression(codegen, err, match->test); if (err) { return err; @@ -1784,9 +1785,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui const GDScriptParser::ForNode *for_n = static_cast<const GDScriptParser::ForNode *>(s); codegen.start_block(); - GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype())); + GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype(), codegen.script)); - gen->start_for(iterator.type, _gdtype_from_datatype(for_n->list->get_datatype())); + gen->start_for(iterator.type, _gdtype_from_datatype(for_n->list->get_datatype(), codegen.script)); GDScriptCodeGenerator::Address list = _parse_expression(codegen, err, for_n->list); if (err) { @@ -1897,13 +1898,14 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui const GDScriptParser::VariableNode *lv = static_cast<const GDScriptParser::VariableNode *>(s); // Should be already in stack when the block began. GDScriptCodeGenerator::Address local = codegen.locals[lv->identifier->name]; - GDScriptParser::DataType local_type = lv->get_datatype(); + GDScriptDataType local_type = _gdtype_from_datatype(lv->get_datatype(), codegen.script); + bool initialized = false; if (lv->initializer != nullptr) { // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. - if (local_type.is_hard_type() && local_type.builtin_type == Variant::ARRAY) { + if (local_type.has_type && local_type.builtin_type == Variant::ARRAY) { if (local_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(local, _gdtype_from_datatype(local_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); } else { codegen.generator->write_construct_array(local, Vector<GDScriptCodeGenerator::Address>()); } @@ -1920,15 +1922,23 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } - } else if (lv->get_datatype().is_hard_type()) { + initialized = true; + } else if (local_type.has_type) { // Initialize with default for type. if (local_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(local, _gdtype_from_datatype(local_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); - } else if (local_type.kind == GDScriptParser::DataType::BUILTIN) { + codegen.generator->write_construct_typed_array(local, local_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); + initialized = true; + } else if (local_type.kind == GDScriptDataType::BUILTIN) { codegen.generator->write_construct(local, local_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); + initialized = true; } // The `else` branch is for objects, in such case we leave it as `null`. } + + // Assigns a null for the unassigned variables in loops. + if (!initialized && p_block->is_loop) { + codegen.generator->write_construct(local, Variant::NIL, Vector<GDScriptCodeGenerator::Address>()); + } } break; case GDScriptParser::Node::CONSTANT: { // Local constants. @@ -2033,17 +2043,17 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ continue; } - GDScriptParser::DataType field_type = field->get_datatype(); + GDScriptDataType field_type = _gdtype_from_datatype(field->get_datatype(), codegen.script); - GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, _gdtype_from_datatype(field->get_datatype())); + GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type); if (field->initializer) { // Emit proper line change. codegen.generator->write_newline(field->initializer->start_line); // For typed arrays we need to make sure this is already initialized correctly so typed assignment work. - if (field_type.is_hard_type() && field_type.builtin_type == Variant::ARRAY) { + if (field_type.has_type && field_type.builtin_type == Variant::ARRAY) { if (field_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(dst_address, _gdtype_from_datatype(field_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); + codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); } else { codegen.generator->write_construct_array(dst_address, Vector<GDScriptCodeGenerator::Address>()); } @@ -2062,13 +2072,13 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ if (src_address.mode == GDScriptCodeGenerator::Address::TEMPORARY) { codegen.generator->pop_temporary(); } - } else if (field->get_datatype().is_hard_type()) { + } else if (field_type.has_type) { codegen.generator->write_newline(field->start_line); // Initialize with default for type. if (field_type.has_container_element_type()) { - codegen.generator->write_construct_typed_array(dst_address, _gdtype_from_datatype(field_type.get_container_element_type(), codegen.script), Vector<GDScriptCodeGenerator::Address>()); - } else if (field_type.kind == GDScriptParser::DataType::BUILTIN) { + codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(), Vector<GDScriptCodeGenerator::Address>()); + } else if (field_type.kind == GDScriptDataType::BUILTIN) { codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); } // The `else` branch is for objects, in such case we leave it as `null`. @@ -2107,8 +2117,8 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ if (EngineDebugger::is_active()) { String signature; // Path. - if (!p_script->get_path().is_empty()) { - signature += p_script->get_path(); + if (!p_script->get_script_path().is_empty()) { + signature += p_script->get_script_path(); } // Location. if (p_func) { @@ -2195,23 +2205,20 @@ Error GDScriptCompiler::_parse_setter_getter(GDScript *p_script, const GDScriptP return err; } -Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { - parsing_classes.insert(p_script); +Error GDScriptCompiler::_populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { + if (parsed_classes.has(p_script)) { + return OK; + } - if (p_class->outer && p_class->outer->outer) { - // Owner is not root - if (!parsed_classes.has(p_script->_owner)) { - if (parsing_classes.has(p_script->_owner)) { - _set_error("Cyclic class reference for '" + String(p_class->identifier->name) + "'.", p_class); - return ERR_PARSE_ERROR; - } - Error err = _parse_class_level(p_script->_owner, p_class->outer, p_keep_state); - if (err) { - return err; - } - } + if (parsing_classes.has(p_script)) { + String class_name = p_class->identifier ? String(p_class->identifier->name) : p_class->fqcn; + _set_error(vformat(R"(Cyclic class reference for "%s".)", class_name), p_class); + return ERR_PARSE_ERROR; } + parsing_classes.insert(p_script); + + p_script->clearing = true; #ifdef TOOLS_ENABLED p_script->doc_functions.clear(); p_script->doc_variables.clear(); @@ -2234,10 +2241,24 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->base = Ref<GDScript>(); p_script->_base = nullptr; p_script->members.clear(); + + // This makes possible to clear script constants and member_functions without heap-use-after-free errors. + HashMap<StringName, Variant> constants; + for (const KeyValue<StringName, Variant> &E : p_script->constants) { + constants.insert(E.key, E.value); + } p_script->constants.clear(); + constants.clear(); + HashMap<StringName, GDScriptFunction *> member_functions; for (const KeyValue<StringName, GDScriptFunction *> &E : p_script->member_functions) { + member_functions.insert(E.key, E.value); + } + p_script->member_functions.clear(); + for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { memdelete(E.value); } + member_functions.clear(); + if (p_script->implicit_initializer) { memdelete(p_script->implicit_initializer); } @@ -2252,8 +2273,9 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar p_script->implicit_initializer = nullptr; p_script->implicit_ready = nullptr; + p_script->clearing = false; + p_script->tool = parser->is_tool(); - p_script->name = p_class->identifier ? p_class->identifier->name : ""; if (!p_script->name.is_empty()) { if (ClassDB::class_exists(p_script->name) && ClassDB::is_class_exposed(p_script->name)) { @@ -2262,53 +2284,50 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar } } - Ref<GDScriptNativeClass> native; + GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type, p_script); - GDScriptDataType base_type = _gdtype_from_datatype(p_class->base_type); // Inheritance switch (base_type.kind) { case GDScriptDataType::NATIVE: { int native_idx = GDScriptLanguage::get_singleton()->get_global_map()[base_type.native_type]; - native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx]; - ERR_FAIL_COND_V(native.is_null(), ERR_BUG); - p_script->native = native; + p_script->native = GDScriptLanguage::get_singleton()->get_global_array()[native_idx]; + ERR_FAIL_COND_V(p_script->native.is_null(), ERR_BUG); } break; case GDScriptDataType::GDSCRIPT: { Ref<GDScript> base = Ref<GDScript>(base_type.script_type); - p_script->base = base; - p_script->_base = base.ptr(); + if (base.is_null()) { + return ERR_COMPILATION_FAILED; + } - if (p_class->base_type.kind == GDScriptParser::DataType::CLASS && p_class->base_type.class_type != nullptr) { - if (p_class->base_type.script_path == main_script->path) { - if (!parsed_classes.has(p_script->_base)) { - if (parsing_classes.has(p_script->_base)) { - String class_name = p_class->identifier ? p_class->identifier->name : "<main>"; - _set_error("Cyclic class reference for '" + class_name + "'.", p_class); - return ERR_PARSE_ERROR; - } - Error err = _parse_class_level(p_script->_base, p_class->base_type.class_type, p_keep_state); - if (err) { - return err; - } - } - } else { - Error err = OK; - base = GDScriptCache::get_full_script(p_class->base_type.script_path, err, main_script->path); - if (err) { - return err; - } - if (base.is_null() || !base->is_valid()) { - return ERR_COMPILATION_FAILED; - } + if (base.ptr() == main_script || main_script->is_subclass(base.ptr())) { + Error err = _populate_class_members(base.ptr(), p_class->base_type.class_type, p_keep_state); + if (err) { + return err; + } + } else if (!base->is_valid()) { + Error err = OK; + Ref<GDScript> base_root = GDScriptCache::get_full_script(base->path, err, p_script->path); + if (err) { + _set_error(vformat(R"(Could not compile base class "%s" from "%s": %s)", base->fully_qualified_name, base->path, error_names[err]), nullptr); + return err; + } + if (base_root.is_valid()) { + base = Ref<GDScript>(base_root->find_class(base->fully_qualified_name)); + } + if (base.is_null()) { + _set_error(vformat(R"(Could not find class "%s" in "%s".)", base->fully_qualified_name, base->path), nullptr); + return ERR_COMPILATION_FAILED; } + ERR_FAIL_COND_V(!base->is_valid() && !base->reloading, ERR_BUG); } + p_script->base = base; + p_script->_base = base.ptr(); p_script->member_indices = base->member_indices; - native = base->native; - p_script->native = native; + p_script->native = base->native; } break; default: { - _set_error("Parser bug: invalid inheritance.", p_class); + _set_error("Parser bug: invalid inheritance.", nullptr); return ERR_BUG; } break; } @@ -2370,6 +2389,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar #ifdef TOOLS_ENABLED if (variable->initializer != nullptr && variable->initializer->is_constant) { p_script->member_default_values[name] = variable->initializer->reduced_value; + GDScriptCompiler::convert_to_initializer_type(p_script->member_default_values[name], variable); } else { p_script->member_default_values.erase(name); } @@ -2478,8 +2498,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar parsed_classes.insert(p_script); parsing_classes.erase(p_script); - //parse sub-classes - + // Populate sub-classes. for (int i = 0; i < p_class->members.size(); i++) { const GDScriptParser::ClassNode::Member &member = p_class->members[i]; if (member.type != member.CLASS) { @@ -2491,27 +2510,24 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar GDScript *subclass_ptr = subclass.ptr(); // Subclass might still be parsing, just skip it - if (!parsed_classes.has(subclass_ptr) && !parsing_classes.has(subclass_ptr)) { - Error err = _parse_class_level(subclass_ptr, inner_class, p_keep_state); + if (!parsing_classes.has(subclass_ptr)) { + Error err = _populate_class_members(subclass_ptr, inner_class, p_keep_state); if (err) { return err; } } #ifdef TOOLS_ENABLED - p_script->member_lines[name] = inner_class->start_line; #endif - p_script->constants.insert(name, subclass); //once parsed, goes to the list of constants } return OK; } -Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { - //parse methods - +Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { + // Compile member functions, getters, and setters. for (int i = 0; i < p_class->members.size(); i++) { const GDScriptParser::ClassNode::Member &member = p_class->members[i]; if (member.type == member.FUNCTION) { @@ -2615,17 +2631,40 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa StringName name = inner_class->identifier->name; GDScript *subclass = p_script->subclasses[name].ptr(); - Error err = _parse_class_blocks(subclass, inner_class, p_keep_state); + Error err = _compile_class(subclass, inner_class, p_keep_state); if (err) { return err; } } +#ifdef TOOLS_ENABLED + p_script->_update_doc(); +#endif + + p_script->_init_rpc_methods_properties(); + p_script->valid = true; return OK; } -void GDScriptCompiler::_make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { +void GDScriptCompiler::convert_to_initializer_type(Variant &p_variant, const GDScriptParser::VariableNode *p_node) { + // Set p_variant to the value of p_node's initializer, with the type of p_node's variable. + GDScriptParser::DataType member_t = p_node->datatype; + GDScriptParser::DataType init_t = p_node->initializer->datatype; + if (member_t.is_hard_type() && init_t.is_hard_type() && + member_t.kind == GDScriptParser::DataType::BUILTIN && init_t.kind == GDScriptParser::DataType::BUILTIN) { + if (Variant::can_convert_strict(init_t.builtin_type, member_t.builtin_type)) { + Variant *v = &p_node->initializer->reduced_value; + Callable::CallError ce; + Variant::construct(member_t.builtin_type, p_variant, const_cast<const Variant **>(&v), 1, ce); + } + } +} + +void GDScriptCompiler::make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { + p_script->fully_qualified_name = p_class->fqcn; + p_script->name = p_class->identifier ? p_class->identifier->name : ""; + HashMap<StringName, Ref<GDScript>> old_subclasses; if (p_keep_state) { @@ -2642,24 +2681,22 @@ void GDScriptCompiler::_make_scripts(GDScript *p_script, const GDScriptParser::C StringName name = inner_class->identifier->name; Ref<GDScript> subclass; - String fully_qualified_name = p_script->fully_qualified_name + "::" + name; if (old_subclasses.has(name)) { subclass = old_subclasses[name]; } else { - Ref<GDScript> orphan_subclass = GDScriptLanguage::get_singleton()->get_orphan_subclass(fully_qualified_name); - if (orphan_subclass.is_valid()) { - subclass = orphan_subclass; - } else { - subclass.instantiate(); - } + subclass = GDScriptLanguage::get_singleton()->get_orphan_subclass(inner_class->fqcn); + } + + if (subclass.is_null()) { + subclass.instantiate(); } subclass->_owner = p_script; - subclass->fully_qualified_name = fully_qualified_name; + subclass->path = p_script->path; p_script->subclasses.insert(name, subclass); - _make_scripts(subclass.ptr(), inner_class, false); + make_scripts(subclass.ptr(), inner_class, p_keep_state); } } @@ -2673,26 +2710,22 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri source = p_script->get_path(); - // The best fully qualified name for a base level script is its file path - p_script->fully_qualified_name = p_script->path; - // Create scripts for subclasses beforehand so they can be referenced - _make_scripts(p_script, root, p_keep_state); + make_scripts(p_script, root, p_keep_state); - p_script->_owner = nullptr; - Error err = _parse_class_level(p_script, root, p_keep_state); + main_script->_owner = nullptr; + Error err = _populate_class_members(main_script, parser->get_tree(), p_keep_state); if (err) { return err; } - err = _parse_class_blocks(p_script, root, p_keep_state); - + err = _compile_class(main_script, root, p_keep_state); if (err) { return err; } - return GDScriptCache::finish_compiling(p_script->get_path()); + return GDScriptCache::finish_compiling(main_script->get_path()); } String GDScriptCompiler::get_error() const { diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index e4264ea55b..fc5aa05190 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -115,13 +115,14 @@ class GDScriptCompiler { bool _is_class_member_property(CodeGen &codegen, const StringName &p_name); bool _is_class_member_property(GDScript *owner, const StringName &p_name); + bool _is_local_or_parameter(CodeGen &codegen, const StringName &p_name); void _set_error(const String &p_error, const GDScriptParser::Node *p_node); Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); - GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner = nullptr) const; + GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner); GDScriptCodeGenerator::Address _parse_assign_right_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::AssignmentNode *p_assignmentint, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); @@ -130,9 +131,8 @@ class GDScriptCompiler { Error _parse_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block, bool p_add_locals = true); GDScriptFunction *_parse_function(Error &r_error, GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::FunctionNode *p_func, bool p_for_ready = false, bool p_for_lambda = false); Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter); - Error _parse_class_level(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); - Error _parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); - void _make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + Error _populate_class_members(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); + Error _compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); int err_line = 0; int err_column = 0; StringName source; @@ -140,6 +140,8 @@ class GDScriptCompiler { bool within_await = false; public: + static void convert_to_initializer_type(Variant &p_variant, const GDScriptParser::VariableNode *p_node); + static void make_scripts(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state); Error compile(const GDScriptParser *p_parser, GDScript *p_script, bool p_keep_state = false); String get_error() const; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index f33d1409bc..79387d1bf6 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1612,7 +1612,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, } } - if (!found) { + if (!found && base.value.get_type() != Variant::NIL) { found = _guess_method_return_type_from_base(c, base, call->function_name, r_type); } } @@ -2272,6 +2272,11 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex if (base_type.class_type->has_function(p_method)) { const GDScriptParser::FunctionNode *method = base_type.class_type->get_member(p_method).function; if (!is_static || method->is_static) { + if (method->get_datatype().is_set() && !method->get_datatype().is_variant()) { + r_type.type = method->get_datatype(); + return true; + } + int last_return_line = -1; const GDScriptParser::ExpressionNode *last_returned_value = nullptr; GDScriptParser::CompletionContext c = p_context; @@ -2285,10 +2290,6 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex if (_guess_expression_type(c, last_returned_value, r_type)) { return true; } - if (method->get_datatype().is_set() && !method->get_datatype().is_variant()) { - r_type.type = method->get_datatype(); - return true; - } } } } @@ -2511,6 +2512,69 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } } +static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::SubscriptNode *p_subscript, GDScriptParser::DataType &r_base_type, Variant *r_base = nullptr) { + if (p_context.base == nullptr) { + return false; + } + const GDScriptParser::GetNodeNode *get_node = nullptr; + + switch (p_subscript->base->type) { + case GDScriptParser::Node::GET_NODE: { + get_node = static_cast<GDScriptParser::GetNodeNode *>(p_subscript->base); + } break; + + case GDScriptParser::Node::IDENTIFIER: { + const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base); + + switch (identifier_node->source) { + case GDScriptParser::IdentifierNode::Source::MEMBER_VARIABLE: { + if (p_context.current_class != nullptr) { + const StringName &member_name = identifier_node->name; + const GDScriptParser::ClassNode *current_class = p_context.current_class; + + if (current_class->has_member(member_name)) { + const GDScriptParser::ClassNode::Member &member = current_class->get_member(member_name); + + if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { + const GDScriptParser::VariableNode *variable = static_cast<GDScriptParser::VariableNode *>(member.variable); + + if (variable->initializer && variable->initializer->type == GDScriptParser::Node::GET_NODE) { + get_node = static_cast<GDScriptParser::GetNodeNode *>(variable->initializer); + } + } + } + } + } break; + case GDScriptParser::IdentifierNode::Source::LOCAL_VARIABLE: { + if (identifier_node->next != nullptr && identifier_node->next->type == GDScriptParser::ClassNode::Node::GET_NODE) { + get_node = static_cast<GDScriptParser::GetNodeNode *>(identifier_node->next); + } + } break; + default: { + } break; + } + } break; + default: { + } break; + } + + if (get_node != nullptr) { + const Object *node = p_context.base->call("get_node_or_null", NodePath(get_node->full_path)); + if (node != nullptr) { + if (r_base != nullptr) { + *r_base = node; + } + r_base_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_base_type.kind = GDScriptParser::DataType::NATIVE; + r_base_type.native_type = node->get_class_name(); + r_base_type.builtin_type = Variant::OBJECT; + return true; + } + } + + return false; +} + static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, const GDScriptParser::Node *p_call, int p_argidx, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, bool &r_forced, String &r_arghint) { if (p_call->type == GDScriptParser::Node::PRELOAD) { if (p_argidx == 0 && bool(EDITOR_GET("text_editor/completion/complete_file_paths"))) { @@ -2562,12 +2626,16 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } if (subscript->is_attribute) { - GDScriptCompletionIdentifier ci; - if (_guess_expression_type(p_context, subscript->base, ci)) { - base_type = ci.type; - base = ci.value; - } else { - return; + bool found_type = _get_subscript_type(p_context, subscript, base_type, &base); + + if (!found_type) { + GDScriptCompletionIdentifier ci; + if (_guess_expression_type(p_context, subscript->base, ci)) { + base_type = ci.type; + base = ci.value; + } else { + return; + } } _static = base_type.is_meta_type; @@ -2765,7 +2833,8 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c const GDScriptParser::SubscriptNode *attr = static_cast<const GDScriptParser::SubscriptNode *>(completion_context.node); if (attr->base) { GDScriptCompletionIdentifier base; - if (!_guess_expression_type(completion_context, attr->base, base)) { + bool found_type = _get_subscript_type(completion_context, attr, base.type); + if (!found_type && !_guess_expression_type(completion_context, attr->base, base)) { break; } @@ -3051,8 +3120,9 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co r_result.type = ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION; r_result.location = base_type.class_type->get_member(p_symbol).get_line(); r_result.class_path = base_type.script_path; - r_result.script = GDScriptCache::get_shallow_script(r_result.class_path); - return OK; + Error err = OK; + r_result.script = GDScriptCache::get_shallow_script(r_result.class_path, err); + return err; } base_type = base_type.class_type->base_type; } @@ -3208,15 +3278,6 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co } } - // Need special checks for assert and preload as they are technically - // keywords, so are not registered in GDScriptUtilityFunctions. - if (GDScriptUtilityFunctions::function_exists(p_symbol) || "assert" == p_symbol || "preload" == p_symbol) { - r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; - r_result.class_name = "@GDScript"; - r_result.class_member = p_symbol; - return OK; - } - if ("PI" == p_symbol || "TAU" == p_symbol || "INF" == p_symbol || "NAN" == p_symbol) { r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT; r_result.class_name = "@GDScript"; @@ -3226,10 +3287,24 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co GDScriptParser parser; parser.parse(p_code, p_path, true); - GDScriptAnalyzer analyzer(&parser); - analyzer.analyze(); GDScriptParser::CompletionContext context = parser.get_completion_context(); + context.base = p_owner; + + // Allows class functions with the names like built-ins to be handled properly. + if (context.type != GDScriptParser::COMPLETION_ATTRIBUTE) { + // Need special checks for assert and preload as they are technically + // keywords, so are not registered in GDScriptUtilityFunctions. + if (GDScriptUtilityFunctions::function_exists(p_symbol) || "assert" == p_symbol || "preload" == p_symbol) { + r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD; + r_result.class_name = "@GDScript"; + r_result.class_member = p_symbol; + return OK; + } + } + + GDScriptAnalyzer analyzer(&parser); + analyzer.analyze(); if (context.current_class && context.current_class->extends.size() > 0) { bool success = false; @@ -3387,7 +3462,9 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co break; } GDScriptCompletionIdentifier base; - if (!_guess_expression_type(context, subscript->base, base)) { + + bool found_type = _get_subscript_type(context, subscript, base.type); + if (!found_type && !_guess_expression_type(context, subscript->base, base)) { break; } diff --git a/modules/gdscript/gdscript_function.cpp b/modules/gdscript/gdscript_function.cpp index 98b3e40f1b..24a614b1ad 100644 --- a/modules/gdscript/gdscript_function.cpp +++ b/modules/gdscript/gdscript_function.cpp @@ -149,10 +149,17 @@ GDScriptFunction::GDScriptFunction() { } GDScriptFunction::~GDScriptFunction() { + get_script()->member_functions.erase(name); + for (int i = 0; i < lambdas.size(); i++) { memdelete(lambdas[i]); } + for (int i = 0; i < argument_types.size(); i++) { + argument_types.write[i].script_type_ref = Ref<Script>(); + } + return_type.script_type_ref = Ref<Script>(); + #ifdef DEBUG_ENABLED MutexLock lock(GDScriptLanguage::get_singleton()->mutex); diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index e44038d6da..6e5f7a8520 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -430,6 +430,7 @@ public: }; private: + friend class GDScript; friend class GDScriptCompiler; friend class GDScriptByteCodeGenerator; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 6842a1ff49..f2aafe9f0c 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -147,6 +147,10 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, varray(), true); // Networking. register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::rpc_annotation, varray("", "", "", 0), true); + +#ifdef DEBUG_ENABLED + is_ignoring_warnings = !(bool)GLOBAL_GET("debug/gdscript/warnings/enable"); +#endif } GDScriptParser::~GDScriptParser() { @@ -230,7 +234,7 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_ warning.leftmost_column = p_source->leftmost_column; warning.rightmost_column = p_source->rightmost_column; - if (warn_level == GDScriptWarning::WarnLevel::ERROR) { + if (warn_level == GDScriptWarning::WarnLevel::ERROR || bool(GLOBAL_GET("debug/gdscript/warnings/treat_warnings_as_errors"))) { push_error(warning.get_message(), p_source); return; } @@ -534,6 +538,7 @@ void GDScriptParser::end_statement(const String &p_context) { void GDScriptParser::parse_program() { head = alloc_node<ClassNode>(); + head->fqcn = script_path; current_class = head; // If we happen to parse an annotation before extends or class_name keywords, track it. @@ -646,6 +651,15 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() { if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the class name after "class".)")) { n_class->identifier = parse_identifier(); + if (n_class->outer) { + String fqcn = n_class->outer->fqcn; + if (fqcn.is_empty()) { + fqcn = script_path; + } + n_class->fqcn = fqcn + "::" + n_class->identifier->name; + } else { + n_class->fqcn = n_class->identifier->name; + } } if (match(GDScriptTokenizer::Token::EXTENDS)) { @@ -684,6 +698,7 @@ GDScriptParser::ClassNode *GDScriptParser::parse_class() { void GDScriptParser::parse_class_name() { if (consume(GDScriptTokenizer::Token::IDENTIFIER, R"(Expected identifier for the global class name after "class_name".)")) { current_class->identifier = parse_identifier(); + current_class->fqcn = String(current_class->identifier->name); } if (match(GDScriptTokenizer::Token::EXTENDS)) { @@ -1530,7 +1545,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, VariableNode *variable = static_cast<VariableNode *>(statement); const SuiteNode::Local &local = current_suite->get_local(variable->identifier->name); if (local.type != SuiteNode::Local::UNDEFINED) { - push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), variable->identifier->name)); + push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", local.get_name(), variable->identifier->name), variable->identifier); } current_suite->add_local(variable, current_function); break; @@ -1545,7 +1560,7 @@ GDScriptParser::SuiteNode *GDScriptParser::parse_suite(const String &p_context, } else { name = "variable"; } - push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, constant->identifier->name)); + push_error(vformat(R"(There is already a %s named "%s" declared in this scope.)", name, constant->identifier->name), constant->identifier); } current_suite->add_local(constant, current_function); break; @@ -1820,9 +1835,9 @@ GDScriptParser::ForNode *GDScriptParser::parse_for() { } suite->add_local(SuiteNode::Local(n_for->variable, current_function)); } - suite->parent_for = n_for; n_for->loop = parse_suite(R"("for" block)", suite); + n_for->loop->is_loop = true; complete_extents(n_for); // Reset break/continue state. @@ -2154,6 +2169,7 @@ GDScriptParser::WhileNode *GDScriptParser::parse_while() { is_continue_match = false; n_while->loop = parse_suite(R"("while" block)"); + n_while->loop->is_loop = true; complete_extents(n_while); // Reset break/continue state. @@ -3725,6 +3741,12 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node // This is called after the analyzer is done finding the type, so this should be set here. DataType export_type = variable->get_datatype(); + if (p_annotation->name == SNAME("@export_range")) { + if (export_type.builtin_type == Variant::INT) { + variable->export_info.type = Variant::INT; + } + } + if (p_annotation->name == SNAME("@export")) { if (variable->datatype_specifier == nullptr && variable->initializer == nullptr) { push_error(R"(Cannot use simple "@export" annotation with variable without type or initializer, since type can't be inferred.)", p_annotation); @@ -3764,15 +3786,14 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node } break; case GDScriptParser::DataType::CLASS: - // Can assume type is a global GDScript class. if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; - variable->export_info.hint_string = export_type.class_type->identifier->name; + variable->export_info.hint_string = export_type.to_string(); } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_NODE_TYPE; - variable->export_info.hint_string = export_type.class_type->identifier->name; + variable->export_info.hint_string = export_type.to_string(); } else { push_error(R"(Export type can only be built-in, a resource, a node or an enum.)", variable); return false; @@ -3781,16 +3802,19 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node break; case GDScriptParser::DataType::SCRIPT: { StringName class_name; - if (export_type.script_type != nullptr && export_type.script_type.is_valid()) { + StringName native_base; + if (export_type.script_type.is_valid()) { class_name = export_type.script_type->get_language()->get_global_class_name(export_type.script_type->get_path()); + native_base = export_type.script_type->get_instance_base_type(); } if (class_name == StringName()) { Ref<Script> script = ResourceLoader::load(export_type.script_path, SNAME("Script")); if (script.is_valid()) { class_name = script->get_language()->get_global_class_name(export_type.script_path); + native_base = script->get_instance_base_type(); } } - if (class_name != StringName() && ClassDB::is_parent_class(ScriptServer::get_global_class_native_base(class_name), SNAME("Resource"))) { + if (class_name != StringName() && native_base != StringName() && ClassDB::is_parent_class(native_base, SNAME("Resource"))) { variable->export_info.type = Variant::OBJECT; variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; variable->export_info.hint_string = class_name; diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index d8f5b866aa..d092a2a5e9 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -786,6 +786,7 @@ public: LOCAL_VARIABLE, LOCAL_ITERATOR, // `for` loop iterator. LOCAL_BIND, // Pattern bind. + MEMBER_SIGNAL, MEMBER_VARIABLE, MEMBER_CONSTANT, INHERITED_VARIABLE, @@ -1054,12 +1055,12 @@ public: HashMap<StringName, int> locals_indices; FunctionNode *parent_function = nullptr; - ForNode *parent_for = nullptr; IfNode *parent_if = nullptr; bool has_return = false; bool has_continue = false; bool has_unreachable_code = false; // Just so warnings aren't given more than once per block. + bool is_loop = false; bool has_local(const StringName &p_name) const; const Local &get_local(const StringName &p_name) const; @@ -1216,13 +1217,14 @@ private: bool can_break = false; bool can_continue = false; bool is_continue_match = false; // Whether a `continue` will act on a `match`. - bool is_ignoring_warnings = false; List<bool> multiline_stack; ClassNode *head = nullptr; Node *list = nullptr; List<ParserError> errors; + #ifdef DEBUG_ENABLED + bool is_ignoring_warnings = false; List<GDScriptWarning> warnings; HashSet<String> ignored_warnings; HashSet<uint32_t> ignored_warning_codes; diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index bcbe8b8d2b..27b6792e84 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -294,6 +294,7 @@ struct GDScriptUtilityFunctionsDefinitions { } GDScript *p = base.ptr(); + String path = p->get_script_path(); Vector<StringName> sname; while (p->_owner) { @@ -302,7 +303,7 @@ struct GDScriptUtilityFunctionsDefinitions { } sname.reverse(); - if (!p->path.is_resource_file()) { + if (!path.is_resource_file()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::DICTIONARY; @@ -317,7 +318,7 @@ struct GDScriptUtilityFunctionsDefinitions { Dictionary d; d["@subpath"] = cp; - d["@path"] = p->get_path(); + d["@path"] = path; for (const KeyValue<StringName, GDScript::MemberInfo> &E : base->member_indices) { if (!d.has(E.key)) { diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index c73ba798aa..fdcc0625d7 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -2227,7 +2227,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } #ifdef DEBUG_ENABLED gdfs->state.function_name = name; - gdfs->state.script_path = _script->get_path(); + gdfs->state.script_path = _script->get_script_path(); #endif gdfs->state.defarg = defarg; gdfs->function = this; diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index a0c107aa53..36bc051643 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -96,7 +96,7 @@ String GDScriptWarning::get_message() const { } break; case RETURN_VALUE_DISCARDED: { CHECK_SYMBOLS(1); - return "The function '" + symbols[0] + "()' returns a value, but this value is never used."; + return "The function '" + symbols[0] + "()' returns a value that will be discarded if not used."; } break; case PROPERTY_USED_AS_FUNCTION: { CHECK_SYMBOLS(2); @@ -171,6 +171,10 @@ int GDScriptWarning::get_default_value(Code p_code) { if (get_name_from_code(p_code).to_lower().begins_with("unsafe_")) { return WarnLevel::IGNORE; } + // Too spammy by default on common cases (connect, Tween, etc.). + if (p_code == RETURN_VALUE_DISCARDED) { + return WarnLevel::IGNORE; + } return WarnLevel::WARN; } diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index de3becbaf8..e442bf8159 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -844,8 +844,9 @@ Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) { lines = p_code.split("\n"); Error err = GDScriptParser::parse(p_code, p_path, false); + GDScriptAnalyzer analyzer(this); + if (err == OK) { - GDScriptAnalyzer analyzer(this); err = analyzer.analyze(); } update_diagnostics(); diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 39f4c976a4..551973140d 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -32,6 +32,7 @@ #include "core/config/project_settings.h" #include "editor/doc_tools.h" +#include "editor/editor_help.h" #include "editor/editor_log.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 15131afde7..f59983ca90 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -71,27 +71,38 @@ void init_autoloads() { continue; } - Ref<Resource> res = ResourceLoader::load(info.path); - ERR_CONTINUE_MSG(res.is_null(), "Can't autoload: " + info.path); Node *n = nullptr; - Ref<PackedScene> scn = res; - Ref<Script> script = res; - if (scn.is_valid()) { - n = scn->instantiate(); - } else if (script.is_valid()) { - StringName ibt = script->get_instance_base_type(); - bool valid_type = ClassDB::is_parent_class(ibt, "Node"); - ERR_CONTINUE_MSG(!valid_type, "Script does not inherit from Node: " + info.path); + if (ResourceLoader::get_resource_type(info.path) == "PackedScene") { + // Cache the scene reference before loading it (for cyclic references) + Ref<PackedScene> scn; + scn.instantiate(); + scn->set_path(info.path); + scn->reload_from_file(); + ERR_CONTINUE_MSG(!scn.is_valid(), vformat("Can't autoload: %s.", info.path)); + + if (scn.is_valid()) { + n = scn->instantiate(); + } + } else { + Ref<Resource> res = ResourceLoader::load(info.path); + ERR_CONTINUE_MSG(res.is_null(), vformat("Can't autoload: %s.", info.path)); - Object *obj = ClassDB::instantiate(ibt); + Ref<Script> scr = res; + if (scr.is_valid()) { + StringName ibt = scr->get_instance_base_type(); + bool valid_type = ClassDB::is_parent_class(ibt, "Node"); + ERR_CONTINUE_MSG(!valid_type, vformat("Script does not inherit from Node: %s.", info.path)); - ERR_CONTINUE_MSG(!obj, "Cannot instance script for autoload, expected 'Node' inheritance, got: " + String(ibt) + "."); + Object *obj = ClassDB::instantiate(ibt); - n = Object::cast_to<Node>(obj); - n->set_script(script); + ERR_CONTINUE_MSG(!obj, vformat("Cannot instance script for Autoload, expected 'Node' inheritance, got: %s.", ibt)); + + n = Object::cast_to<Node>(obj); + n->set_script(scr); + } } - ERR_CONTINUE_MSG(!n, "Path in autoload not a node or script: " + info.path); + ERR_CONTINUE_MSG(!n, vformat("Path in autoload not a node or script: %s.", info.path)); n->set_name(info.name); for (int i = 0; i < ScriptServer::get_language_count(); i++) { @@ -251,7 +262,10 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) { return false; } } else { - if (next.get_extension().to_lower() == "gd") { + if (next.ends_with(".notest.gd")) { + next = dir->get_next(); + continue; + } else if (next.get_extension().to_lower() == "gd") { #ifndef DEBUG_ENABLED // On release builds, skip tests marked as debug only. Error open_err = OK; @@ -461,7 +475,6 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { Ref<GDScript> script; script.instantiate(); script->set_path(source_file); - script->set_script_path(source_file); err = script->load_source_code(source_file); if (err != OK) { enable_stdout(); @@ -598,6 +611,9 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { } enable_stdout(); + + GDScriptCache::remove_script(script->get_path()); + return result; } diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.gd new file mode 100644 index 0000000000..4dd2b556ee --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.gd @@ -0,0 +1,9 @@ +# https://github.com/godotengine/godot/issues/62957 + +func test(): + var dict = { + &"key": "StringName", + "key": "String" + } + + print("Invalid dictionary: %s" % dict) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.out new file mode 100644 index 0000000000..189d8a7955 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/dictionary_string_stringname_equivalent.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Key "key" was already used in this dictionary (at line 5). diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out index 3baeb17066..4ccd2da381 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_less.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function(int)". +The function signature doesn't match the parent. Parent signature is "my_function(int) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out index 3baeb17066..4ccd2da381 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_count_more.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function(int)". +The function signature doesn't match the parent. Parent signature is "my_function(int) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out index 665c229339..c70a1df10d 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_default_values.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function(int = default)". +The function signature doesn't match the parent. Parent signature is "my_function(int = default) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out index 3baeb17066..4ccd2da381 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_parameter_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function(int)". +The function signature doesn't match the parent. Parent signature is "my_function(int) -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out index 5b22739a93..61004ff627 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/function_dont_match_parent_signature_return_type.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -The function signature doesn't match the parent. Parent signature is "int my_function()". +The function signature doesn't match the parent. Parent signature is "my_function() -> int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.gd b/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.gd new file mode 100644 index 0000000000..5c8b9fa4ae --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.gd @@ -0,0 +1,6 @@ +extends Node + +var script: int + +func test(): + pass diff --git a/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.out b/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.out new file mode 100644 index 0000000000..8454aaa404 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/overload_script_variable.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Member "script" redefined (original in native class 'Node') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.gd b/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.gd new file mode 100644 index 0000000000..28561ff94b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.gd @@ -0,0 +1,9 @@ +func test(): + pass + +class A: + func overload_me(): + pass + +class B extends A: + var overload_me diff --git a/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.out b/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.out new file mode 100644 index 0000000000..32357f9f6a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/variable_overloads_superclass_function.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +The member "overload_me" already exists in parent class A. diff --git a/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.gd new file mode 100644 index 0000000000..4511c3d10b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.gd @@ -0,0 +1,8 @@ +func test(): + # Converted to String when initialized + var string_array: Array[String] = [&"abc"] + print(string_array) + + # Converted to StringName when initialized + var stringname_array: Array[StringName] = ["abc"] + print(stringname_array) diff --git a/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.out new file mode 100644 index 0000000000..70dd01d88e --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/array_string_stringname_equivalent.out @@ -0,0 +1,3 @@ +GDTEST_OK +["abc"] +[&"abc"] diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.gd new file mode 100644 index 0000000000..7881a0feb6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.gd @@ -0,0 +1,14 @@ +const A: = preload("base_outer_resolution_a.notest.gd") +const B: = preload("base_outer_resolution_b.notest.gd") +const C: = preload("base_outer_resolution_c.notest.gd") + +const Extend: = preload("base_outer_resolution_extend.notest.gd") + +func test() -> void: + Extend.test_a(A.new()) + Extend.test_b(B.new()) + Extend.InnerClass.test_c(C.new()) + Extend.InnerClass.InnerInnerClass.test_a_b_c(A.new(), B.new(), C.new()) + Extend.InnerClass.InnerInnerClass.test_enum(C.TestEnum.HELLO_WORLD) + Extend.InnerClass.InnerInnerClass.test_a_prime(A.APrime.new()) + diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.out b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.out new file mode 100644 index 0000000000..bd27bd31f6 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution.out @@ -0,0 +1,7 @@ +GDTEST_OK +true +true +true +true +true +true diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_a.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_a.notest.gd new file mode 100644 index 0000000000..966c8bfc8f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_a.notest.gd @@ -0,0 +1,2 @@ +class APrime: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_b.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_b.notest.gd new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_b.notest.gd diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_base.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_base.notest.gd new file mode 100644 index 0000000000..666b147ced --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_base.notest.gd @@ -0,0 +1,4 @@ +const A: = preload("base_outer_resolution_a.notest.gd") + +class InnerClassInBase: + const C: = preload("base_outer_resolution_c.notest.gd") diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_c.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_c.notest.gd new file mode 100644 index 0000000000..814be35314 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_c.notest.gd @@ -0,0 +1,3 @@ +enum TestEnum { + HELLO_WORLD +} diff --git a/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_extend.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_extend.notest.gd new file mode 100644 index 0000000000..fbd28779d4 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/base_outer_resolution_extend.notest.gd @@ -0,0 +1,23 @@ +extends "base_outer_resolution_base.notest.gd" + +const B: = preload("base_outer_resolution_b.notest.gd") + +static func test_a(a: A) -> void: + print(a is A) + +static func test_b(b: B) -> void: + print(b is B) + +class InnerClass extends InnerClassInBase: + static func test_c(c: C) -> void: + print(c is C) + + class InnerInnerClass: + static func test_a_b_c(a: A, b: B, c: C) -> void: + print(a is A and b is B and c is C) + + static func test_enum(test_enum: C.TestEnum) -> void: + print(test_enum == C.TestEnum.HELLO_WORLD) + + static func test_a_prime(a_prime: A.APrime) -> void: + print(a_prime is A.APrime) diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.gd b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.gd new file mode 100644 index 0000000000..3f9bfe189c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.gd @@ -0,0 +1,4 @@ +extends "inner_base.gd".InnerA.InnerAB + +func test(): + super.test() diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.out b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.out new file mode 100644 index 0000000000..62f1383392 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_inner_base.out @@ -0,0 +1,3 @@ +GDTEST_OK +InnerA.InnerAB.test +InnerB.test diff --git a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.gd b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.notest.gd index ea744e3027..c3fc176679 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.notest.gd @@ -1,7 +1,4 @@ const A := 42 -func test(): - pass - func something(): return "OK" diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_base.gd b/modules/gdscript/tests/scripts/analyzer/features/inner_base.gd new file mode 100644 index 0000000000..a825b59255 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/inner_base.gd @@ -0,0 +1,18 @@ +extends InnerA + +func test(): + super.test() + +class InnerA extends InnerAB: + func test(): + print("InnerA.test") + super.test() + + class InnerAB extends InnerB: + func test(): + print("InnerA.InnerAB.test") + super.test() + +class InnerB: + func test(): + print("InnerB.test") diff --git a/modules/gdscript/tests/scripts/analyzer/features/inner_base.out b/modules/gdscript/tests/scripts/analyzer/features/inner_base.out new file mode 100644 index 0000000000..ddd5ffcfd3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/inner_base.out @@ -0,0 +1,4 @@ +GDTEST_OK +InnerA.test +InnerA.InnerAB.test +InnerB.test diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd index 276875dd5a..9d0324ead8 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_constant_types_are_inferred.gd @@ -1,4 +1,4 @@ -const Constants = preload("gdscript_to_preload.gd") +const Constants = preload("gdscript_to_preload.notest.gd") func test(): var a := Constants.A diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.gd new file mode 100644 index 0000000000..b730453a8a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.gd @@ -0,0 +1,4 @@ +const A = preload("preload_cyclic_reference_a.notest.gd") + +func test(): + A.test_cyclic_reference() diff --git a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.out b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.out index d73c5eb7cd..14bb971221 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/gdscript_to_preload.out +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference.out @@ -1 +1,2 @@ GDTEST_OK +godot diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_a.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_a.notest.gd new file mode 100644 index 0000000000..7a6035ded1 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_a.notest.gd @@ -0,0 +1,12 @@ +const B = preload("preload_cyclic_reference_b.notest.gd") + +const WAITING_FOR = "godot" + +static func test_cyclic_reference(): + B.test_cyclic_reference() + +static func test_cyclic_reference_2(): + B.test_cyclic_reference_2() + +static func test_cyclic_reference_3(): + B.test_cyclic_reference_3() diff --git a/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_b.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_b.notest.gd new file mode 100644 index 0000000000..3ea5b01156 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/preload_cyclic_reference_b.notest.gd @@ -0,0 +1,10 @@ +const A = preload("preload_cyclic_reference_a.notest.gd") + +static func test_cyclic_reference(): + A.test_cyclic_reference_2() + +static func test_cyclic_reference_2(): + A.test_cyclic_reference_3() + +static func test_cyclic_reference_3(): + print(A.WAITING_FOR) diff --git a/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd index 5f73064cc0..beabf3d2e5 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/use_preload_script_as_type.gd @@ -1,4 +1,4 @@ -const preloaded : GDScript = preload("gdscript_to_preload.gd") +const preloaded : GDScript = preload("gdscript_to_preload.notest.gd") func test(): var preloaded_instance: preloaded = preloaded.new() diff --git a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out index 13f759dd46..e89bb9226f 100644 --- a/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out +++ b/modules/gdscript/tests/scripts/parser/warnings/return_value_discarded.out @@ -2,4 +2,4 @@ GDTEST_OK >> WARNING >> Line: 6 >> RETURN_VALUE_DISCARDED ->> The function 'i_return_int()' returns a value, but this value is never used. +>> The function 'i_return_int()' returns a value that will be discarded if not used. diff --git a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd new file mode 100644 index 0000000000..5303fb04e2 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd @@ -0,0 +1,35 @@ +# https://github.com/godotengine/godot/issues/63965 + +func test(): + var array_str: Array = [] + array_str.push_back("godot") + print("StringName in Array: ", &"godot" in array_str) + + var array_sname: Array = [] + array_sname.push_back(&"godot") + print("String in Array: ", "godot" in array_sname) + + # Not equal because the values are different types. + print("Arrays not equal: ", array_str != array_sname) + + var string_array: Array[String] = [] + var stringname_array: Array[StringName] = [] + + assert(!string_array.push_back(&"abc")) + print("Array[String] insert converted: ", typeof(string_array[0]) == TYPE_STRING) + + assert(!stringname_array.push_back("abc")) + print("Array[StringName] insert converted: ", typeof(stringname_array[0]) == TYPE_STRING_NAME) + + print("StringName in Array[String]: ", &"abc" in string_array) + print("String in Array[StringName]: ", "abc" in stringname_array) + + var packed_string_array: PackedStringArray = [] + assert(!packed_string_array.push_back("abc")) + print("StringName in PackedStringArray: ", &"abc" in packed_string_array) + + assert(!string_array.push_back("abc")) + print("StringName finds String in Array: ", string_array.find(&"abc")) + + assert(!stringname_array.push_back(&"abc")) + print("String finds StringName in Array: ", stringname_array.find("abc")) diff --git a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.out new file mode 100644 index 0000000000..98ab78e8f1 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.out @@ -0,0 +1,11 @@ +GDTEST_OK +StringName in Array: true +String in Array: true +Arrays not equal: true +Array[String] insert converted: true +Array[StringName] insert converted: true +StringName in Array[String]: true +String in Array[StringName]: true +StringName in PackedStringArray: true +StringName finds String in Array: 0 +String finds StringName in Array: 0 diff --git a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd new file mode 100644 index 0000000000..1f15026f17 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd @@ -0,0 +1,17 @@ +# https://github.com/godotengine/godot/issues/62957 + +func test(): + var string_dict = {} + string_dict["abc"] = 42 + var stringname_dict = {} + stringname_dict[&"abc"] = 24 + + print("String key is TYPE_STRING: ", typeof(string_dict.keys()[0]) == TYPE_STRING) + print("StringName key is TYPE_STRING: ", typeof(stringname_dict.keys()[0]) == TYPE_STRING) + + print("StringName gets String: ", string_dict.get(&"abc")) + print("String gets StringName: ", stringname_dict.get("abc")) + + stringname_dict[&"abc"] = 42 + # They compare equal because StringName keys are converted to String. + print("String Dictionary == StringName Dictionary: ", string_dict == stringname_dict) diff --git a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out new file mode 100644 index 0000000000..ab5b89d55c --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out @@ -0,0 +1,6 @@ +GDTEST_OK +String key is TYPE_STRING: true +StringName key is TYPE_STRING: true +StringName gets String: 42 +String gets StringName: 24 +String Dictionary == StringName Dictionary: true diff --git a/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd new file mode 100644 index 0000000000..55be021a90 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.gd @@ -0,0 +1,14 @@ +# https://github.com/godotengine/godot/issues/60145 + +func test(): + match "abc": + &"abc": + print("String matched StringName") + _: + print("no match") + + match &"abc": + "abc": + print("StringName matched String") + _: + print("no match") diff --git a/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out new file mode 100644 index 0000000000..9d5a18da3d --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/match_string_stringname_equivalent.out @@ -0,0 +1,3 @@ +GDTEST_OK +String matched StringName +StringName matched String diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd new file mode 100644 index 0000000000..f33ba7dffd --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd @@ -0,0 +1,25 @@ +# https://github.com/godotengine/godot/pull/69620 + +var a: int = 1 + +func shadow_regular_assignment(a: Variant, b: Variant) -> void: + print(a) + print(self.a) + a = b + print(a) + print(self.a) + + +var v := Vector2(0.0, 0.0) + +func shadow_subscript_assignment(v: Vector2, x: float) -> void: + print(v) + print(self.v) + v.x += x + print(v) + print(self.v) + + +func test(): + shadow_regular_assignment('a', 'b') + shadow_subscript_assignment(Vector2(1.0, 1.0), 5.0) diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out new file mode 100644 index 0000000000..5b981bc8bb --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out @@ -0,0 +1,17 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> SHADOWED_VARIABLE +>> The local function parameter "a" is shadowing an already-declared variable at line 3. +>> WARNING +>> Line: 15 +>> SHADOWED_VARIABLE +>> The local function parameter "v" is shadowing an already-declared variable at line 13. +a +1 +b +1 +(1, 1) +(0, 0) +(6, 1) +(0, 0) diff --git a/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd new file mode 100644 index 0000000000..f8bd46523e --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.gd @@ -0,0 +1,11 @@ +# https://github.com/godotengine/godot/issues/64171 + +func test(): + print("Compare ==: ", "abc" == &"abc") + print("Compare ==: ", &"abc" == "abc") + print("Compare !=: ", "abc" != &"abc") + print("Compare !=: ", &"abc" != "abc") + + print("Concat: ", "abc" + &"def") + print("Concat: ", &"abc" + "def") + print("Concat: ", &"abc" + &"def") diff --git a/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out new file mode 100644 index 0000000000..7e9c364b60 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/string_stringname_equivalent.out @@ -0,0 +1,8 @@ +GDTEST_OK +Compare ==: true +Compare ==: true +Compare !=: false +Compare !=: false +Concat: abcdef +Concat: abcdef +Concat: abcdef diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml index 3cd0f5c0f9..f313f4b28f 100644 --- a/modules/gltf/doc_classes/GLTFDocument.xml +++ b/modules/gltf/doc_classes/GLTFDocument.xml @@ -14,8 +14,9 @@ <param index="1" name="base_path" type="String" /> <param index="2" name="state" type="GLTFState" /> <param index="3" name="flags" type="int" default="0" /> - <param index="4" name="bake_fps" type="int" default="30" /> <description> + Takes a [PackedByteArray] defining a gLTF and returns a [GLTFState] object through the [param state] parameter. + [b]Note:[/b] The [param base_path] tells [method append_from_buffer] where to find dependencies and can be empty. </description> </method> <method name="append_from_file"> @@ -23,9 +24,10 @@ <param index="0" name="path" type="String" /> <param index="1" name="state" type="GLTFState" /> <param index="2" name="flags" type="int" default="0" /> - <param index="3" name="bake_fps" type="int" default="30" /> - <param index="4" name="base_path" type="String" default="""" /> + <param index="3" name="base_path" type="String" default="""" /> <description> + Takes a path to a gLTF file and returns a [GLTFState] object through the [param state] parameter. + [b]Note:[/b] The [param base_path] tells [method append_from_file] where to find dependencies and can be empty. </description> </method> <method name="append_from_scene"> @@ -33,21 +35,40 @@ <param index="0" name="node" type="Node" /> <param index="1" name="state" type="GLTFState" /> <param index="2" name="flags" type="int" default="0" /> - <param index="3" name="bake_fps" type="int" default="30" /> <description> + Takes a Godot Engine scene node and returns a [GLTFState] object through the [param state] parameter. </description> </method> <method name="generate_buffer"> <return type="PackedByteArray" /> <param index="0" name="state" type="GLTFState" /> <description> + Takes a [GLTFState] object through the [param state] parameter and returns a gLTF [PackedByteArray]. </description> </method> <method name="generate_scene"> <return type="Node" /> <param index="0" name="state" type="GLTFState" /> - <param index="1" name="bake_fps" type="int" default="30" /> + <param index="1" name="bake_fps" type="float" default="30" /> + <param index="2" name="trimming" type="bool" default="false" /> <description> + Takes a [GLTFState] object through the [param state] parameter and returns a Godot Engine scene node. + </description> + </method> + <method name="register_gltf_document_extension" qualifiers="static"> + <return type="void" /> + <param index="0" name="extension" type="GLTFDocumentExtension" /> + <param index="1" name="first_priority" type="bool" default="false" /> + <description> + Registers the given [GLTFDocumentExtension] instance with GLTFDocument. If [param first_priority] is true, this extension will be run first. Otherwise, it will be run last. + [b]Note:[/b] Like GLTFDocument itself, all GLTFDocumentExtension classes must be stateless in order to function properly. If you need to store data, use the [code]set_additional_data[/code] and [code]get_additional_data[/code] methods in [GLTFState] or [GLTFNode]. + </description> + </method> + <method name="unregister_gltf_document_extension" qualifiers="static"> + <return type="void" /> + <param index="0" name="extension" type="GLTFDocumentExtension" /> + <description> + Unregisters the given [GLTFDocumentExtension] instance. </description> </method> <method name="write_to_filesystem"> @@ -55,11 +76,9 @@ <param index="0" name="state" type="GLTFState" /> <param index="1" name="path" type="String" /> <description> + Takes a [GLTFState] object through the [param state] parameter and writes a glTF file to the filesystem. + [b]Note:[/b] The extension of the glTF file determines if it is a .glb binary file or a .gltf file. </description> </method> </methods> - <members> - <member name="extensions" type="GLTFDocumentExtension[]" setter="set_extensions" getter="get_extensions" default="[]"> - </member> - </members> </class> diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml index 936794976d..ac638a8962 100644 --- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml +++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml @@ -5,10 +5,22 @@ </brief_description> <description> Extends the functionality of the [GLTFDocument] class by allowing you to run arbitrary code at various stages of GLTF import or export. + To use, make a new class extending GLTFDocumentExtension, override any methods you need, make an instance of your class, and register it using [method GLTFDocument.register_gltf_document_extension]. + [b]Note:[/b] Like GLTFDocument itself, all GLTFDocumentExtension classes must be stateless in order to function properly. If you need to store data, use the [code]set_additional_data[/code] and [code]get_additional_data[/code] methods in [GLTFState] or [GLTFNode]. </description> <tutorials> </tutorials> <methods> + <method name="_convert_scene_node" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="state" type="GLTFState" /> + <param index="1" name="gltf_node" type="GLTFNode" /> + <param index="2" name="scene_node" type="Node" /> + <description> + Part of the export process. This method is run after [method _export_preflight] and before [method _export_node]. + Runs when converting the data from a Godot scene node. This method can be used to process the Godot scene node data into a format that can be used by [method _export_node]. + </description> + </method> <method name="_export_node" qualifiers="virtual"> <return type="int" /> <param index="0" name="state" type="GLTFState" /> @@ -16,23 +28,40 @@ <param index="2" name="json" type="Dictionary" /> <param index="3" name="node" type="Node" /> <description> + Part of the export process. This method is run after [method _convert_scene_node] and before [method _export_post]. + This method can be used to modify the final JSON of each node. </description> </method> <method name="_export_post" qualifiers="virtual"> <return type="int" /> <param index="0" name="state" type="GLTFState" /> <description> + Part of the export process. This method is run last, after all other parts of the export process. + This method can be used to modify the final JSON of the generated GLTF file. </description> </method> <method name="_export_preflight" qualifiers="virtual"> <return type="int" /> <param index="0" name="root" type="Node" /> <description> + Part of the export process. This method is run first, before all other parts of the export process. + The return value is used to determine if this [GLTFDocumentExtension] instance should be used for exporting a given GLTF file. If [constant OK], the export will use this [GLTFDocumentExtension] instance. If not overridden, [constant OK] is returned. + </description> + </method> + <method name="_generate_scene_node" qualifiers="virtual"> + <return type="Node3D" /> + <param index="0" name="state" type="GLTFState" /> + <param index="1" name="gltf_node" type="GLTFNode" /> + <param index="2" name="scene_parent" type="Node" /> + <description> + Part of the import process. This method is run after [method _parse_node_extensions] and before [method _import_post_parse]. + Runs when generating a Godot scene node from a GLTFNode. The returned node will be added to the scene tree. Multiple nodes can be generated in this step if they are added as a child of the returned node. </description> </method> <method name="_get_supported_extensions" qualifiers="virtual"> <return type="PackedStringArray" /> <description> + Part of the import process. This method is run after [method _import_preflight] and before [method _parse_node_extensions]. Returns an array of the GLTF extensions supported by this GLTFDocumentExtension class. This is used to validate if a GLTF file with required extensions can be loaded. </description> </method> @@ -43,6 +72,8 @@ <param index="2" name="json" type="Dictionary" /> <param index="3" name="node" type="Node" /> <description> + Part of the import process. This method is run after [method _import_post_parse] and before [method _import_post]. + This method can be used to make modifications to each of the generated Godot scene nodes. </description> </method> <method name="_import_post" qualifiers="virtual"> @@ -50,18 +81,35 @@ <param index="0" name="state" type="GLTFState" /> <param index="1" name="root" type="Node" /> <description> + Part of the import process. This method is run last, after all other parts of the import process. + This method can be used to modify the final Godot scene generated by the import process. </description> </method> <method name="_import_post_parse" qualifiers="virtual"> <return type="int" /> <param index="0" name="state" type="GLTFState" /> <description> + Part of the import process. This method is run after [method _generate_scene_node] and before [method _import_node]. + This method can be used to modify any of the data imported so far, including any scene nodes, before running the final per-node import step. </description> </method> <method name="_import_preflight" qualifiers="virtual"> <return type="int" /> <param index="0" name="state" type="GLTFState" /> + <param index="1" name="extensions" type="PackedStringArray" /> + <description> + Part of the import process. This method is run first, before all other parts of the import process. + The return value is used to determine if this [GLTFDocumentExtension] instance should be used for importing a given GLTF file. If [constant OK], the import will use this [GLTFDocumentExtension] instance. If not overridden, [constant OK] is returned. + </description> + </method> + <method name="_parse_node_extensions" qualifiers="virtual"> + <return type="int" /> + <param index="0" name="state" type="GLTFState" /> + <param index="1" name="gltf_node" type="GLTFNode" /> + <param index="2" name="extensions" type="Dictionary" /> <description> + Part of the import process. This method is run after [method _get_supported_extensions] and before [method _generate_scene_node]. + Runs when parsing the node extensions of a GLTFNode. This method can be used to process the extension JSON data into a format that can be used by [method _generate_scene_node]. The return value should be a member of the [enum Error] enum. </description> </method> </methods> diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml index d0740cf7ca..9a554a0d49 100644 --- a/modules/gltf/doc_classes/GLTFState.xml +++ b/modules/gltf/doc_classes/GLTFState.xml @@ -66,7 +66,7 @@ </description> </method> <method name="get_materials"> - <return type="BaseMaterial3D[]" /> + <return type="Material[]" /> <description> </description> </method> @@ -169,7 +169,7 @@ </method> <method name="set_materials"> <return type="void" /> - <param index="0" name="materials" type="BaseMaterial3D[]" /> + <param index="0" name="materials" type="Material[]" /> <description> </description> </method> diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp index 95db1c0965..fe63afcc56 100644 --- a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp +++ b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp @@ -33,18 +33,10 @@ #include "editor_scene_exporter_gltf_plugin.h" #include "../gltf_document.h" -#include "../gltf_state.h" -#include "core/config/project_settings.h" -#include "core/error/error_list.h" -#include "core/object/object.h" -#include "core/templates/vector.h" #include "editor/editor_file_dialog.h" #include "editor/editor_file_system.h" #include "editor/editor_node.h" -#include "scene/3d/mesh_instance_3d.h" -#include "scene/gui/check_box.h" -#include "scene/main/node.h" String SceneExporterGLTFPlugin::get_name() const { return "ConvertGLTF2"; @@ -85,7 +77,7 @@ void SceneExporterGLTFPlugin::_gltf2_dialog_action(String p_file) { state.instantiate(); int32_t flags = 0; flags |= EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS; - Error err = doc->append_from_scene(root, state, flags, 30.0f); + Error err = doc->append_from_scene(root, state, flags); if (err != OK) { ERR_PRINT(vformat("glTF2 save scene error %s.", itos(err))); } diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.h b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.h index 5af46bc752..66fd27b449 100644 --- a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.h +++ b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.h @@ -36,6 +36,8 @@ #include "editor/editor_plugin.h" #include "editor_scene_importer_gltf.h" +class EditorFileDialog; + class SceneExporterGLTFPlugin : public EditorPlugin { GDCLASS(SceneExporterGLTFPlugin, EditorPlugin); diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index 20c9508474..4dafa746bc 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -33,7 +33,6 @@ #ifdef TOOLS_ENABLED #include "../gltf_document.h" -#include "../gltf_state.h" #include "core/config/project_settings.h" #include "editor/editor_file_dialog.h" @@ -41,8 +40,7 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "main/main.h" -#include "scene/main/node.h" -#include "scene/resources/animation.h" +#include "scene/gui/line_edit.h" #ifdef WINDOWS_ENABLED // Code by Pedro Estebanez (https://github.com/godotengine/godot/pull/59766) @@ -58,7 +56,7 @@ void EditorSceneFormatImporterBlend::get_extensions(List<String> *r_extensions) } Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_t p_flags, - const HashMap<StringName, Variant> &p_options, int p_bake_fps, + const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err) { // Get global paths for source and sink. @@ -227,14 +225,14 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ if (p_options.has(SNAME("blender/materials/unpack_enabled")) && p_options[SNAME("blender/materials/unpack_enabled")]) { base_dir = sink.get_base_dir(); } - Error err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, p_bake_fps, base_dir); + Error err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, base_dir); if (err != OK) { if (r_err) { *r_err = FAILED; } return nullptr; } - return gltf->generate_scene(state, p_bake_fps); + return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"]); } Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, diff --git a/modules/gltf/editor/editor_scene_importer_blend.h b/modules/gltf/editor/editor_scene_importer_blend.h index a1485ff82e..fe687f19fc 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.h +++ b/modules/gltf/editor/editor_scene_importer_blend.h @@ -66,7 +66,7 @@ public: virtual uint32_t get_import_flags() const override; virtual void get_extensions(List<String> *r_extensions) const override; virtual Node *import_scene(const String &p_path, uint32_t p_flags, - const HashMap<StringName, Variant> &p_options, int p_bake_fps, + const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err = nullptr) override; virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) override; diff --git a/modules/gltf/editor/editor_scene_importer_fbx.cpp b/modules/gltf/editor/editor_scene_importer_fbx.cpp index 017a44cccf..fb5fb455b8 100644 --- a/modules/gltf/editor/editor_scene_importer_fbx.cpp +++ b/modules/gltf/editor/editor_scene_importer_fbx.cpp @@ -33,12 +33,9 @@ #ifdef TOOLS_ENABLED #include "../gltf_document.h" -#include "../gltf_state.h" #include "core/config/project_settings.h" #include "editor/editor_settings.h" -#include "scene/main/node.h" -#include "scene/resources/animation.h" uint32_t EditorSceneFormatImporterFBX::get_import_flags() const { return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION; @@ -49,7 +46,7 @@ void EditorSceneFormatImporterFBX::get_extensions(List<String> *r_extensions) co } Node *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_t p_flags, - const HashMap<StringName, Variant> &p_options, int p_bake_fps, + const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err) { // Get global paths for source and sink. @@ -95,14 +92,14 @@ Node *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_t Ref<GLTFState> state; state.instantiate(); print_verbose(vformat("glTF path: %s", sink)); - Error err = gltf->append_from_file(sink, state, p_flags, p_bake_fps); + Error err = gltf->append_from_file(sink, state, p_flags); if (err != OK) { if (r_err) { *r_err = FAILED; } return nullptr; } - return gltf->generate_scene(state, p_bake_fps); + return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"]); } Variant EditorSceneFormatImporterFBX::get_option_visibility(const String &p_path, bool p_for_animation, diff --git a/modules/gltf/editor/editor_scene_importer_fbx.h b/modules/gltf/editor/editor_scene_importer_fbx.h index b0039b1c8f..6bf9f3e033 100644 --- a/modules/gltf/editor/editor_scene_importer_fbx.h +++ b/modules/gltf/editor/editor_scene_importer_fbx.h @@ -45,7 +45,7 @@ public: virtual uint32_t get_import_flags() const override; virtual void get_extensions(List<String> *r_extensions) const override; virtual Node *import_scene(const String &p_path, uint32_t p_flags, - const HashMap<StringName, Variant> &p_options, int p_bake_fps, + const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err = nullptr) override; virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) override; diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp index 161808aade..bd1ba85abf 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.cpp +++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp @@ -33,9 +33,6 @@ #include "editor_scene_importer_gltf.h" #include "../gltf_document.h" -#include "../gltf_state.h" - -#include "scene/resources/animation.h" uint32_t EditorSceneFormatImporterGLTF::get_import_flags() const { return ImportFlags::IMPORT_SCENE | ImportFlags::IMPORT_ANIMATION; @@ -47,13 +44,13 @@ void EditorSceneFormatImporterGLTF::get_extensions(List<String> *r_extensions) c } Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t p_flags, - const HashMap<StringName, Variant> &p_options, int p_bake_fps, + const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err) { Ref<GLTFDocument> doc; doc.instantiate(); Ref<GLTFState> state; state.instantiate(); - Error err = doc->append_from_file(p_path, state, p_flags, p_bake_fps); + Error err = doc->append_from_file(p_path, state, p_flags); if (err != OK) { if (r_err) { *r_err = err; @@ -63,7 +60,12 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t if (p_options.has("animation/import")) { state->set_create_animations(bool(p_options["animation/import"])); } - return doc->generate_scene(state, p_bake_fps); + + if (p_options.has("animation/trimming")) { + return doc->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"]); + } else { + return doc->generate_scene(state, (float)p_options["animation/fps"], false); + } } #endif // TOOLS_ENABLED diff --git a/modules/gltf/editor/editor_scene_importer_gltf.h b/modules/gltf/editor/editor_scene_importer_gltf.h index b17a1e4eaa..c0582b26c1 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.h +++ b/modules/gltf/editor/editor_scene_importer_gltf.h @@ -33,9 +33,6 @@ #ifdef TOOLS_ENABLED -#include "../gltf_document_extension.h" -#include "../gltf_state.h" - #include "editor/import/resource_importer_scene.h" class Animation; @@ -48,7 +45,7 @@ public: virtual uint32_t get_import_flags() const override; virtual void get_extensions(List<String> *r_extensions) const override; virtual Node *import_scene(const String &p_path, uint32_t p_flags, - const HashMap<StringName, Variant> &p_options, int p_bake_fps, + const HashMap<StringName, Variant> &p_options, List<String> *r_missing_deps, Error *r_err = nullptr) override; }; diff --git a/modules/gltf/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp index 713779712c..f997fe8f66 100644 --- a/modules/gltf/gltf_document_extension.cpp +++ b/modules/gltf/extensions/gltf_document_extension.cpp @@ -31,50 +31,77 @@ #include "gltf_document_extension.h" void GLTFDocumentExtension::_bind_methods() { + // Import process. + GDVIRTUAL_BIND(_import_preflight, "state", "extensions"); GDVIRTUAL_BIND(_get_supported_extensions); - GDVIRTUAL_BIND(_import_preflight, "state"); + GDVIRTUAL_BIND(_parse_node_extensions, "state", "gltf_node", "extensions"); + GDVIRTUAL_BIND(_generate_scene_node, "state", "gltf_node", "scene_parent"); GDVIRTUAL_BIND(_import_post_parse, "state"); GDVIRTUAL_BIND(_import_node, "state", "gltf_node", "json", "node"); GDVIRTUAL_BIND(_import_post, "state", "root"); + // Export process. GDVIRTUAL_BIND(_export_preflight, "root"); + GDVIRTUAL_BIND(_convert_scene_node, "state", "gltf_node", "scene_node"); GDVIRTUAL_BIND(_export_node, "state", "gltf_node", "json", "node"); GDVIRTUAL_BIND(_export_post, "state"); } +// Import process. +Error GLTFDocumentExtension::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) { + ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + int err = OK; + GDVIRTUAL_CALL(_import_preflight, p_state, p_extensions, err); + return Error(err); +} + Vector<String> GLTFDocumentExtension::get_supported_extensions() { Vector<String> ret; GDVIRTUAL_CALL(_get_supported_extensions, ret); return ret; } -Error GLTFDocumentExtension::import_post(Ref<GLTFState> p_state, Node *p_root) { - ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER); +Error GLTFDocumentExtension::parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions) { ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER); int err = OK; - GDVIRTUAL_CALL(_import_post, p_state, p_root, err); + GDVIRTUAL_CALL(_parse_node_extensions, p_state, p_gltf_node, p_extensions, err); return Error(err); } -Error GLTFDocumentExtension::import_preflight(Ref<GLTFState> p_state) { +Node3D *GLTFDocumentExtension::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) { + ERR_FAIL_NULL_V(p_state, nullptr); + ERR_FAIL_NULL_V(p_gltf_node, nullptr); + ERR_FAIL_NULL_V(p_scene_parent, nullptr); + Node3D *ret_node = nullptr; + GDVIRTUAL_CALL(_generate_scene_node, p_state, p_gltf_node, p_scene_parent, ret_node); + return ret_node; +} + +Error GLTFDocumentExtension::import_post_parse(Ref<GLTFState> p_state) { ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); int err = OK; - GDVIRTUAL_CALL(_import_preflight, p_state, err); + GDVIRTUAL_CALL(_import_post_parse, p_state, err); return Error(err); } -Error GLTFDocumentExtension::import_post_parse(Ref<GLTFState> p_state) { +Error GLTFDocumentExtension::import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) { ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER); int err = OK; - GDVIRTUAL_CALL(_import_post_parse, p_state, err); + GDVIRTUAL_CALL(_import_node, p_state, p_gltf_node, r_dict, p_node, err); return Error(err); } -Error GLTFDocumentExtension::export_post(Ref<GLTFState> p_state) { +Error GLTFDocumentExtension::import_post(Ref<GLTFState> p_state, Node *p_root) { + ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER); ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); int err = OK; - GDVIRTUAL_CALL(_export_post, p_state, err); + GDVIRTUAL_CALL(_import_post, p_state, p_root, err); return Error(err); } + +// Export process. Error GLTFDocumentExtension::export_preflight(Node *p_root) { ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER); int err = OK; @@ -82,20 +109,25 @@ Error GLTFDocumentExtension::export_preflight(Node *p_root) { return Error(err); } -Error GLTFDocumentExtension::import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) { +void GLTFDocumentExtension::convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node) { + ERR_FAIL_NULL(p_state); + ERR_FAIL_NULL(p_gltf_node); + ERR_FAIL_NULL(p_scene_node); + GDVIRTUAL_CALL(_convert_scene_node, p_state, p_gltf_node, p_scene_node); +} + +Error GLTFDocumentExtension::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) { ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER); ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER); int err = OK; - GDVIRTUAL_CALL(_import_node, p_state, p_gltf_node, r_dict, p_node, err); + GDVIRTUAL_CALL(_export_node, p_state, p_gltf_node, r_dict, p_node, err); return Error(err); } -Error GLTFDocumentExtension::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) { +Error GLTFDocumentExtension::export_post(Ref<GLTFState> p_state) { ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER); int err = OK; - GDVIRTUAL_CALL(_export_node, p_state, p_gltf_node, r_dict, p_node, err); + GDVIRTUAL_CALL(_export_post, p_state, err); return Error(err); } diff --git a/modules/gltf/gltf_document_extension.h b/modules/gltf/extensions/gltf_document_extension.h index d4bb3993dc..7cc9ca592f 100644 --- a/modules/gltf/gltf_document_extension.h +++ b/modules/gltf/extensions/gltf_document_extension.h @@ -31,8 +31,7 @@ #ifndef GLTF_DOCUMENT_EXTENSION_H #define GLTF_DOCUMENT_EXTENSION_H -#include "gltf_state.h" -#include "structures/gltf_node.h" +#include "../gltf_state.h" class GLTFDocumentExtension : public Resource { GDCLASS(GLTFDocumentExtension, Resource); @@ -41,20 +40,31 @@ protected: static void _bind_methods(); public: + // Import process. + virtual Error import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions); virtual Vector<String> get_supported_extensions(); - virtual Error import_preflight(Ref<GLTFState> p_state); + virtual Error parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions); + virtual Node3D *generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent); virtual Error import_post_parse(Ref<GLTFState> p_state); - virtual Error export_post(Ref<GLTFState> p_state); + virtual Error import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_json, Node *p_node); virtual Error import_post(Ref<GLTFState> p_state, Node *p_node); + // Export process. virtual Error export_preflight(Node *p_state); - virtual Error import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_json, Node *p_node); + virtual void convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node); virtual Error export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_json, Node *p_node); + virtual Error export_post(Ref<GLTFState> p_state); + + // Import process. + GDVIRTUAL2R(int, _import_preflight, Ref<GLTFState>, Vector<String>); GDVIRTUAL0R(Vector<String>, _get_supported_extensions); - GDVIRTUAL1R(int, _import_preflight, Ref<GLTFState>); + GDVIRTUAL3R(int, _parse_node_extensions, Ref<GLTFState>, Ref<GLTFNode>, Dictionary); + GDVIRTUAL3R(Node3D *, _generate_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *); GDVIRTUAL1R(int, _import_post_parse, Ref<GLTFState>); GDVIRTUAL4R(int, _import_node, Ref<GLTFState>, Ref<GLTFNode>, Dictionary, Node *); GDVIRTUAL2R(int, _import_post, Ref<GLTFState>, Node *); + // Export process. GDVIRTUAL1R(int, _export_preflight, Node *); + GDVIRTUAL3(_convert_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *); GDVIRTUAL4R(int, _export_node, Ref<GLTFState>, Ref<GLTFNode>, Dictionary, Node *); GDVIRTUAL1R(int, _export_post, Ref<GLTFState>); }; diff --git a/modules/gltf/gltf_document_extension_convert_importer_mesh.cpp b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp index 1620900a04..cfa498af65 100644 --- a/modules/gltf/gltf_document_extension_convert_importer_mesh.cpp +++ b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp @@ -30,9 +30,7 @@ #include "gltf_document_extension_convert_importer_mesh.h" -#include "gltf_state.h" - -#include "core/error/error_macros.h" +#include "scene/3d/importer_mesh_instance_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/resources/importer_mesh.h" diff --git a/modules/gltf/gltf_document_extension_convert_importer_mesh.h b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h index 00e664e73f..4fbfa0e066 100644 --- a/modules/gltf/gltf_document_extension_convert_importer_mesh.h +++ b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h @@ -33,10 +33,6 @@ #include "gltf_document_extension.h" -#include "scene/3d/importer_mesh_instance_3d.h" -#include "scene/3d/mesh_instance_3d.h" -#include "scene/resources/importer_mesh.h" - class GLTFDocumentExtensionConvertImporterMesh : public GLTFDocumentExtension { GDCLASS(GLTFDocumentExtensionConvertImporterMesh, GLTFDocumentExtension); diff --git a/modules/gltf/extensions/gltf_light.cpp b/modules/gltf/extensions/gltf_light.cpp index d00bead61c..0379c62c9d 100644 --- a/modules/gltf/extensions/gltf_light.cpp +++ b/modules/gltf/extensions/gltf_light.cpp @@ -30,6 +30,8 @@ #include "gltf_light.h" +#include "scene/3d/light_3d.h" + void GLTFLight::_bind_methods() { ClassDB::bind_static_method("GLTFLight", D_METHOD("from_node", "light_node"), &GLTFLight::from_node); ClassDB::bind_method(D_METHOD("to_node"), &GLTFLight::to_node); diff --git a/modules/gltf/extensions/gltf_light.h b/modules/gltf/extensions/gltf_light.h index 04980e144c..85284f1d0e 100644 --- a/modules/gltf/extensions/gltf_light.h +++ b/modules/gltf/extensions/gltf_light.h @@ -31,9 +31,9 @@ #ifndef GLTF_LIGHT_H #define GLTF_LIGHT_H -#include "core/config/engine.h" #include "core/io/resource.h" -#include "scene/3d/light_3d.h" + +class Light3D; // https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual diff --git a/modules/gltf/extensions/gltf_spec_gloss.cpp b/modules/gltf/extensions/gltf_spec_gloss.cpp index 83af91bfcc..0645f31e01 100644 --- a/modules/gltf/extensions/gltf_spec_gloss.cpp +++ b/modules/gltf/extensions/gltf_spec_gloss.cpp @@ -30,6 +30,8 @@ #include "gltf_spec_gloss.h" +#include "core/io/image.h" + void GLTFSpecGloss::_bind_methods() { ClassDB::bind_method(D_METHOD("get_diffuse_img"), &GLTFSpecGloss::get_diffuse_img); ClassDB::bind_method(D_METHOD("set_diffuse_img", "diffuse_img"), &GLTFSpecGloss::set_diffuse_img); diff --git a/modules/gltf/extensions/gltf_spec_gloss.h b/modules/gltf/extensions/gltf_spec_gloss.h index 2b4d3ee609..56474acd03 100644 --- a/modules/gltf/extensions/gltf_spec_gloss.h +++ b/modules/gltf/extensions/gltf_spec_gloss.h @@ -31,9 +31,10 @@ #ifndef GLTF_SPEC_GLOSS_H #define GLTF_SPEC_GLOSS_H -#include "core/io/image.h" #include "core/io/resource.h" +class Image; + // KHR_materials_pbrSpecularGlossiness is an archived GLTF extension. // This means that it is deprecated and not recommended for new files. // However, it is still supported for loading old files. diff --git a/modules/gltf/gltf_defines.h b/modules/gltf/gltf_defines.h index 23bf33869e..7b990e6573 100644 --- a/modules/gltf/gltf_defines.h +++ b/modules/gltf/gltf_defines.h @@ -36,9 +36,10 @@ // Godot classes used by GLTF headers. class BoneAttachment3D; class CSGShape3D; -class DirectionalLight3D; class GridMap; +class ImporterMeshInstance3D; class Light3D; +class MeshInstance3D; class MultiMeshInstance3D; class Skeleton3D; class Skin; diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 99803ed05d..2cb2d21854 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -31,33 +31,23 @@ #include "gltf_document.h" #include "extensions/gltf_spec_gloss.h" -#include "gltf_document_extension.h" -#include "gltf_document_extension_convert_importer_mesh.h" -#include "gltf_state.h" #include "core/crypto/crypto_core.h" -#include "core/error/error_macros.h" #include "core/io/dir_access.h" #include "core/io/file_access.h" #include "core/io/file_access_memory.h" #include "core/io/json.h" #include "core/io/stream_peer.h" #include "core/math/disjoint_set.h" -#include "core/math/vector2.h" -#include "core/variant/dictionary.h" -#include "core/variant/typed_array.h" -#include "core/variant/variant.h" #include "core/version.h" #include "drivers/png/png_driver_common.h" -#include "scene/2d/node_2d.h" +#include "scene/3d/bone_attachment_3d.h" +#include "scene/3d/camera_3d.h" +#include "scene/3d/importer_mesh_instance_3d.h" +#include "scene/3d/light_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/multimesh_instance_3d.h" -#include "scene/3d/node_3d.h" -#include "scene/animation/animation_player.h" -#include "scene/resources/importer_mesh.h" -#include "scene/resources/material.h" -#include "scene/resources/mesh.h" -#include "scene/resources/multimesh.h" +#include "scene/resources/skin.h" #include "scene/resources/surface_tool.h" #include "modules/modules_enabled.gen.h" // For csg, gridmap. @@ -113,148 +103,147 @@ static Ref<ImporterMesh> _mesh_to_importer_mesh(Ref<Mesh> p_mesh) { return importer_mesh; } -Error GLTFDocument::_serialize(Ref<GLTFState> state, const String &p_path) { - if (!state->buffers.size()) { - state->buffers.push_back(Vector<uint8_t>()); +Error GLTFDocument::_serialize(Ref<GLTFState> p_state, const String &p_path) { + if (!p_state->buffers.size()) { + p_state->buffers.push_back(Vector<uint8_t>()); } /* STEP CONVERT MESH INSTANCES */ - _convert_mesh_instances(state); + _convert_mesh_instances(p_state); /* STEP SERIALIZE CAMERAS */ - Error err = _serialize_cameras(state); + Error err = _serialize_cameras(p_state); if (err != OK) { return Error::FAILED; } /* STEP 3 CREATE SKINS */ - err = _serialize_skins(state); + err = _serialize_skins(p_state); if (err != OK) { return Error::FAILED; } /* STEP SERIALIZE MESHES (we have enough info now) */ - err = _serialize_meshes(state); + err = _serialize_meshes(p_state); if (err != OK) { return Error::FAILED; } /* STEP SERIALIZE TEXTURES */ - err = _serialize_materials(state); + err = _serialize_materials(p_state); if (err != OK) { return Error::FAILED; } /* STEP SERIALIZE TEXTURE SAMPLERS */ - err = _serialize_texture_samplers(state); + err = _serialize_texture_samplers(p_state); if (err != OK) { return Error::FAILED; } /* STEP SERIALIZE ANIMATIONS */ - err = _serialize_animations(state); + err = _serialize_animations(p_state); if (err != OK) { return Error::FAILED; } /* STEP SERIALIZE ACCESSORS */ - err = _encode_accessors(state); + err = _encode_accessors(p_state); if (err != OK) { return Error::FAILED; } /* STEP SERIALIZE IMAGES */ - err = _serialize_images(state, p_path); + err = _serialize_images(p_state, p_path); if (err != OK) { return Error::FAILED; } /* STEP SERIALIZE TEXTURES */ - err = _serialize_textures(state); + err = _serialize_textures(p_state); if (err != OK) { return Error::FAILED; } - for (GLTFBufferViewIndex i = 0; i < state->buffer_views.size(); i++) { - state->buffer_views.write[i]->buffer = 0; + for (GLTFBufferViewIndex i = 0; i < p_state->buffer_views.size(); i++) { + p_state->buffer_views.write[i]->buffer = 0; } /* STEP SERIALIZE BUFFER VIEWS */ - err = _encode_buffer_views(state); + err = _encode_buffer_views(p_state); if (err != OK) { return Error::FAILED; } /* STEP SERIALIZE NODES */ - err = _serialize_nodes(state); + err = _serialize_nodes(p_state); if (err != OK) { return Error::FAILED; } /* STEP SERIALIZE SCENE */ - err = _serialize_scenes(state); + err = _serialize_scenes(p_state); if (err != OK) { return Error::FAILED; } /* STEP SERIALIZE LIGHTS */ - err = _serialize_lights(state); + err = _serialize_lights(p_state); if (err != OK) { return Error::FAILED; } /* STEP SERIALIZE EXTENSIONS */ - err = _serialize_gltf_extensions(state); + err = _serialize_gltf_extensions(p_state); if (err != OK) { return Error::FAILED; } /* STEP SERIALIZE VERSION */ - err = _serialize_version(state); + err = _serialize_version(p_state); if (err != OK) { return Error::FAILED; } - for (int32_t ext_i = 0; ext_i < document_extensions.size(); ext_i++) { - Ref<GLTFDocumentExtension> ext = document_extensions[ext_i]; + for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); - err = ext->export_post(state); + err = ext->export_post(p_state); ERR_FAIL_COND_V(err != OK, err); } return OK; } -Error GLTFDocument::_serialize_gltf_extensions(Ref<GLTFState> state) const { - Vector<String> extensions_used = state->extensions_used; - Vector<String> extensions_required = state->extensions_required; - if (!state->lights.is_empty()) { +Error GLTFDocument::_serialize_gltf_extensions(Ref<GLTFState> p_state) const { + Vector<String> extensions_used = p_state->extensions_used; + Vector<String> extensions_required = p_state->extensions_required; + if (!p_state->lights.is_empty()) { extensions_used.push_back("KHR_lights_punctual"); } - if (state->use_khr_texture_transform) { + if (p_state->use_khr_texture_transform) { extensions_used.push_back("KHR_texture_transform"); extensions_required.push_back("KHR_texture_transform"); } if (!extensions_used.is_empty()) { extensions_used.sort(); - state->json["extensionsUsed"] = extensions_used; + p_state->json["extensionsUsed"] = extensions_used; } if (!extensions_required.is_empty()) { extensions_required.sort(); - state->json["extensionsRequired"] = extensions_required; + p_state->json["extensionsRequired"] = extensions_required; } return OK; } -Error GLTFDocument::_serialize_scenes(Ref<GLTFState> state) { +Error GLTFDocument::_serialize_scenes(Ref<GLTFState> p_state) { Array scenes; const int loaded_scene = 0; - state->json["scene"] = loaded_scene; + p_state->json["scene"] = loaded_scene; - if (state->nodes.size()) { + if (p_state->nodes.size()) { Dictionary s; - if (!state->scene_name.is_empty()) { - s["name"] = state->scene_name; + if (!p_state->scene_name.is_empty()) { + s["name"] = p_state->scene_name; } Array nodes; @@ -262,21 +251,21 @@ Error GLTFDocument::_serialize_scenes(Ref<GLTFState> state) { s["nodes"] = nodes; scenes.push_back(s); } - state->json["scenes"] = scenes; + p_state->json["scenes"] = scenes; return OK; } -Error GLTFDocument::_parse_json(const String &p_path, Ref<GLTFState> state) { +Error GLTFDocument::_parse_json(const String &p_path, Ref<GLTFState> p_state) { Error err; - Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err); - if (f.is_null()) { + Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err); + if (file.is_null()) { return err; } Vector<uint8_t> array; - array.resize(f->get_length()); - f->get_buffer(array.ptrw(), array.size()); + array.resize(file->get_length()); + file->get_buffer(array.ptrw(), array.size()); String text; text.parse_utf8((const char *)array.ptr(), array.size()); @@ -286,26 +275,26 @@ Error GLTFDocument::_parse_json(const String &p_path, Ref<GLTFState> state) { _err_print_error("", p_path.utf8().get_data(), json.get_error_line(), json.get_error_message().utf8().get_data(), false, ERR_HANDLER_SCRIPT); return err; } - state->json = json.get_data(); + p_state->json = json.get_data(); return OK; } -Error GLTFDocument::_parse_glb(Ref<FileAccess> f, Ref<GLTFState> state) { - ERR_FAIL_NULL_V(f, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(state, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(f->get_position() != 0, ERR_FILE_CANT_READ); - uint32_t magic = f->get_32(); +Error GLTFDocument::_parse_glb(Ref<FileAccess> p_file, Ref<GLTFState> p_state) { + ERR_FAIL_NULL_V(p_file, ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_file->get_position() != 0, ERR_FILE_CANT_READ); + uint32_t magic = p_file->get_32(); ERR_FAIL_COND_V(magic != 0x46546C67, ERR_FILE_UNRECOGNIZED); //glTF - f->get_32(); // version - f->get_32(); // length - uint32_t chunk_length = f->get_32(); - uint32_t chunk_type = f->get_32(); + p_file->get_32(); // version + p_file->get_32(); // length + uint32_t chunk_length = p_file->get_32(); + uint32_t chunk_type = p_file->get_32(); ERR_FAIL_COND_V(chunk_type != 0x4E4F534A, ERR_PARSE_ERROR); //JSON Vector<uint8_t> json_data; json_data.resize(chunk_length); - uint32_t len = f->get_buffer(json_data.ptrw(), chunk_length); + uint32_t len = p_file->get_buffer(json_data.ptrw(), chunk_length); ERR_FAIL_COND_V(len != chunk_length, ERR_FILE_CORRUPT); String text; @@ -318,21 +307,21 @@ Error GLTFDocument::_parse_glb(Ref<FileAccess> f, Ref<GLTFState> state) { return err; } - state->json = json.get_data(); + p_state->json = json.get_data(); //data? - chunk_length = f->get_32(); - chunk_type = f->get_32(); + chunk_length = p_file->get_32(); + chunk_type = p_file->get_32(); - if (f->eof_reached()) { + if (p_file->eof_reached()) { return OK; //all good } ERR_FAIL_COND_V(chunk_type != 0x004E4942, ERR_PARSE_ERROR); //BIN - state->glb_data.resize(chunk_length); - len = f->get_buffer(state->glb_data.ptrw(), chunk_length); + p_state->glb_data.resize(chunk_length); + len = p_file->get_buffer(p_state->glb_data.ptrw(), chunk_length); ERR_FAIL_COND_V(len != chunk_length, ERR_FILE_CORRUPT); return OK; @@ -405,11 +394,11 @@ static Vector<real_t> _xform_to_array(const Transform3D p_transform) { return array; } -Error GLTFDocument::_serialize_nodes(Ref<GLTFState> state) { +Error GLTFDocument::_serialize_nodes(Ref<GLTFState> p_state) { Array nodes; - for (int i = 0; i < state->nodes.size(); i++) { + for (int i = 0; i < p_state->nodes.size(); i++) { Dictionary node; - Ref<GLTFNode> gltf_node = state->nodes[i]; + Ref<GLTFNode> gltf_node = p_state->nodes[i]; Dictionary extensions; node["extensions"] = extensions; if (!gltf_node->get_name().is_empty()) { @@ -454,21 +443,20 @@ Error GLTFDocument::_serialize_nodes(Ref<GLTFState> state) { node["children"] = children; } - for (int32_t ext_i = 0; ext_i < document_extensions.size(); ext_i++) { - Ref<GLTFDocumentExtension> ext = document_extensions[ext_i]; + for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); - ERR_CONTINUE(!state->scene_nodes.find(i)); - Error err = ext->export_node(state, gltf_node, node, state->scene_nodes[i]); + ERR_CONTINUE(!p_state->scene_nodes.find(i)); + Error err = ext->export_node(p_state, gltf_node, node, p_state->scene_nodes[i]); ERR_CONTINUE(err != OK); } nodes.push_back(node); } - state->json["nodes"] = nodes; + p_state->json["nodes"] = nodes; return OK; } -String GLTFDocument::_gen_unique_name(Ref<GLTFState> state, const String &p_name) { +String GLTFDocument::_gen_unique_name(Ref<GLTFState> p_state, const String &p_name) { const String s_name = p_name.validate_node_name(); String u_name; @@ -479,13 +467,13 @@ String GLTFDocument::_gen_unique_name(Ref<GLTFState> state, const String &p_name if (index > 1) { u_name += itos(index); } - if (!state->unique_names.has(u_name)) { + if (!p_state->unique_names.has(u_name)) { break; } index++; } - state->unique_names.insert(u_name); + p_state->unique_names.insert(u_name); return u_name; } @@ -501,7 +489,7 @@ String GLTFDocument::_sanitize_animation_name(const String &p_name) { return anim_name; } -String GLTFDocument::_gen_unique_animation_name(Ref<GLTFState> state, const String &p_name) { +String GLTFDocument::_gen_unique_animation_name(Ref<GLTFState> p_state, const String &p_name) { const String s_name = _sanitize_animation_name(p_name); String u_name; @@ -512,13 +500,13 @@ String GLTFDocument::_gen_unique_animation_name(Ref<GLTFState> state, const Stri if (index > 1) { u_name += itos(index); } - if (!state->unique_animation_names.has(u_name)) { + if (!p_state->unique_animation_names.has(u_name)) { break; } index++; } - state->unique_animation_names.insert(u_name); + p_state->unique_animation_names.insert(u_name); return u_name; } @@ -530,7 +518,7 @@ String GLTFDocument::_sanitize_bone_name(const String &p_name) { return bone_name; } -String GLTFDocument::_gen_unique_bone_name(Ref<GLTFState> state, const GLTFSkeletonIndex skel_i, const String &p_name) { +String GLTFDocument::_gen_unique_bone_name(Ref<GLTFState> p_state, const GLTFSkeletonIndex p_skel_i, const String &p_name) { String s_name = _sanitize_bone_name(p_name); if (s_name.is_empty()) { s_name = "bone"; @@ -543,23 +531,23 @@ String GLTFDocument::_gen_unique_bone_name(Ref<GLTFState> state, const GLTFSkele if (index > 1) { u_name += "_" + itos(index); } - if (!state->skeletons[skel_i]->unique_names.has(u_name)) { + if (!p_state->skeletons[p_skel_i]->unique_names.has(u_name)) { break; } index++; } - state->skeletons.write[skel_i]->unique_names.insert(u_name); + p_state->skeletons.write[p_skel_i]->unique_names.insert(u_name); return u_name; } -Error GLTFDocument::_parse_scenes(Ref<GLTFState> state) { - ERR_FAIL_COND_V(!state->json.has("scenes"), ERR_FILE_CORRUPT); - const Array &scenes = state->json["scenes"]; +Error GLTFDocument::_parse_scenes(Ref<GLTFState> p_state) { + ERR_FAIL_COND_V(!p_state->json.has("scenes"), ERR_FILE_CORRUPT); + const Array &scenes = p_state->json["scenes"]; int loaded_scene = 0; - if (state->json.has("scene")) { - loaded_scene = state->json["scene"]; + if (p_state->json.has("scene")) { + loaded_scene = p_state->json["scene"]; } else { WARN_PRINT("The load-time scene is not defined in the glTF2 file. Picking the first scene."); } @@ -570,22 +558,22 @@ Error GLTFDocument::_parse_scenes(Ref<GLTFState> state) { ERR_FAIL_COND_V(!s.has("nodes"), ERR_UNAVAILABLE); const Array &nodes = s["nodes"]; for (int j = 0; j < nodes.size(); j++) { - state->root_nodes.push_back(nodes[j]); + p_state->root_nodes.push_back(nodes[j]); } if (s.has("name") && !String(s["name"]).is_empty() && !((String)s["name"]).begins_with("Scene")) { - state->scene_name = _gen_unique_name(state, s["name"]); + p_state->scene_name = _gen_unique_name(p_state, s["name"]); } else { - state->scene_name = _gen_unique_name(state, state->filename); + p_state->scene_name = _gen_unique_name(p_state, p_state->filename); } } return OK; } -Error GLTFDocument::_parse_nodes(Ref<GLTFState> state) { - ERR_FAIL_COND_V(!state->json.has("nodes"), ERR_FILE_CORRUPT); - const Array &nodes = state->json["nodes"]; +Error GLTFDocument::_parse_nodes(Ref<GLTFState> p_state) { + ERR_FAIL_COND_V(!p_state->json.has("nodes"), ERR_FILE_CORRUPT); + const Array &nodes = p_state->json["nodes"]; for (int i = 0; i < nodes.size(); i++) { Ref<GLTFNode> node; node.instantiate(); @@ -629,6 +617,11 @@ Error GLTFDocument::_parse_nodes(Ref<GLTFState> state) { node->light = light; } } + for (Ref<GLTFDocumentExtension> ext : document_extensions) { + ERR_CONTINUE(ext.is_null()); + Error err = ext->parse_node_extensions(p_state, node, extensions); + ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing node extensions for node " + node->get_name() + " in file " + p_state->filename + ". Continuing."); + } } if (n.has("children")) { @@ -638,35 +631,35 @@ Error GLTFDocument::_parse_nodes(Ref<GLTFState> state) { } } - state->nodes.push_back(node); + p_state->nodes.push_back(node); } // build the hierarchy - for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); node_i++) { - for (int j = 0; j < state->nodes[node_i]->children.size(); j++) { - GLTFNodeIndex child_i = state->nodes[node_i]->children[j]; + for (GLTFNodeIndex node_i = 0; node_i < p_state->nodes.size(); node_i++) { + for (int j = 0; j < p_state->nodes[node_i]->children.size(); j++) { + GLTFNodeIndex child_i = p_state->nodes[node_i]->children[j]; - ERR_FAIL_INDEX_V(child_i, state->nodes.size(), ERR_FILE_CORRUPT); - ERR_CONTINUE(state->nodes[child_i]->parent != -1); //node already has a parent, wtf. + ERR_FAIL_INDEX_V(child_i, p_state->nodes.size(), ERR_FILE_CORRUPT); + ERR_CONTINUE(p_state->nodes[child_i]->parent != -1); //node already has a parent, wtf. - state->nodes.write[child_i]->parent = node_i; + p_state->nodes.write[child_i]->parent = node_i; } } - _compute_node_heights(state); + _compute_node_heights(p_state); return OK; } -void GLTFDocument::_compute_node_heights(Ref<GLTFState> state) { - state->root_nodes.clear(); - for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); ++node_i) { - Ref<GLTFNode> node = state->nodes[node_i]; +void GLTFDocument::_compute_node_heights(Ref<GLTFState> p_state) { + p_state->root_nodes.clear(); + for (GLTFNodeIndex node_i = 0; node_i < p_state->nodes.size(); ++node_i) { + Ref<GLTFNode> node = p_state->nodes[node_i]; node->height = 0; GLTFNodeIndex current_i = node_i; while (current_i >= 0) { - const GLTFNodeIndex parent_i = state->nodes[current_i]->parent; + const GLTFNodeIndex parent_i = p_state->nodes[current_i]->parent; if (parent_i >= 0) { ++node->height; } @@ -674,7 +667,7 @@ void GLTFDocument::_compute_node_heights(Ref<GLTFState> state) { } if (node->height == 0) { - state->root_nodes.push_back(node_i); + p_state->root_nodes.push_back(node_i); } } } @@ -697,86 +690,86 @@ static Vector<uint8_t> _parse_base64_uri(const String &uri) { return buf; } -Error GLTFDocument::_encode_buffer_glb(Ref<GLTFState> state, const String &p_path) { - print_verbose("glTF: Total buffers: " + itos(state->buffers.size())); +Error GLTFDocument::_encode_buffer_glb(Ref<GLTFState> p_state, const String &p_path) { + print_verbose("glTF: Total buffers: " + itos(p_state->buffers.size())); - if (!state->buffers.size()) { + if (!p_state->buffers.size()) { return OK; } Array buffers; - if (state->buffers.size()) { - Vector<uint8_t> buffer_data = state->buffers[0]; + if (p_state->buffers.size()) { + Vector<uint8_t> buffer_data = p_state->buffers[0]; Dictionary gltf_buffer; gltf_buffer["byteLength"] = buffer_data.size(); buffers.push_back(gltf_buffer); } - for (GLTFBufferIndex i = 1; i < state->buffers.size() - 1; i++) { - Vector<uint8_t> buffer_data = state->buffers[i]; + for (GLTFBufferIndex i = 1; i < p_state->buffers.size() - 1; i++) { + Vector<uint8_t> buffer_data = p_state->buffers[i]; Dictionary gltf_buffer; String filename = p_path.get_basename().get_file() + itos(i) + ".bin"; String path = p_path.get_base_dir() + "/" + filename; Error err; - Ref<FileAccess> f = FileAccess::open(path, FileAccess::WRITE, &err); - if (f.is_null()) { + Ref<FileAccess> file = FileAccess::open(path, FileAccess::WRITE, &err); + if (file.is_null()) { return err; } if (buffer_data.size() == 0) { return OK; } - f->create(FileAccess::ACCESS_RESOURCES); - f->store_buffer(buffer_data.ptr(), buffer_data.size()); + file->create(FileAccess::ACCESS_RESOURCES); + file->store_buffer(buffer_data.ptr(), buffer_data.size()); gltf_buffer["uri"] = filename; gltf_buffer["byteLength"] = buffer_data.size(); buffers.push_back(gltf_buffer); } - state->json["buffers"] = buffers; + p_state->json["buffers"] = buffers; return OK; } -Error GLTFDocument::_encode_buffer_bins(Ref<GLTFState> state, const String &p_path) { - print_verbose("glTF: Total buffers: " + itos(state->buffers.size())); +Error GLTFDocument::_encode_buffer_bins(Ref<GLTFState> p_state, const String &p_path) { + print_verbose("glTF: Total buffers: " + itos(p_state->buffers.size())); - if (!state->buffers.size()) { + if (!p_state->buffers.size()) { return OK; } Array buffers; - for (GLTFBufferIndex i = 0; i < state->buffers.size(); i++) { - Vector<uint8_t> buffer_data = state->buffers[i]; + for (GLTFBufferIndex i = 0; i < p_state->buffers.size(); i++) { + Vector<uint8_t> buffer_data = p_state->buffers[i]; Dictionary gltf_buffer; String filename = p_path.get_basename().get_file() + itos(i) + ".bin"; String path = p_path.get_base_dir() + "/" + filename; Error err; - Ref<FileAccess> f = FileAccess::open(path, FileAccess::WRITE, &err); - if (f.is_null()) { + Ref<FileAccess> file = FileAccess::open(path, FileAccess::WRITE, &err); + if (file.is_null()) { return err; } if (buffer_data.size() == 0) { return OK; } - f->create(FileAccess::ACCESS_RESOURCES); - f->store_buffer(buffer_data.ptr(), buffer_data.size()); + file->create(FileAccess::ACCESS_RESOURCES); + file->store_buffer(buffer_data.ptr(), buffer_data.size()); gltf_buffer["uri"] = filename; gltf_buffer["byteLength"] = buffer_data.size(); buffers.push_back(gltf_buffer); } - state->json["buffers"] = buffers; + p_state->json["buffers"] = buffers; return OK; } -Error GLTFDocument::_parse_buffers(Ref<GLTFState> state, const String &p_base_path) { - if (!state->json.has("buffers")) { +Error GLTFDocument::_parse_buffers(Ref<GLTFState> p_state, const String &p_base_path) { + if (!p_state->json.has("buffers")) { return OK; } - const Array &buffers = state->json["buffers"]; + const Array &buffers = p_state->json["buffers"]; for (GLTFBufferIndex i = 0; i < buffers.size(); i++) { - if (i == 0 && state->glb_data.size()) { - state->buffers.push_back(state->glb_data); + if (i == 0 && p_state->glb_data.size()) { + p_state->buffers.push_back(p_state->glb_data); } else { const Dictionary &buffer = buffers[i]; @@ -795,29 +788,29 @@ Error GLTFDocument::_parse_buffers(Ref<GLTFState> state, const String &p_base_pa ERR_FAIL_COND_V(p_base_path.is_empty(), ERR_INVALID_PARAMETER); uri = uri.uri_decode(); uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows. - buffer_data = FileAccess::get_file_as_array(uri); + buffer_data = FileAccess::get_file_as_bytes(uri); ERR_FAIL_COND_V_MSG(buffer.size() == 0, ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri); } ERR_FAIL_COND_V(!buffer.has("byteLength"), ERR_PARSE_ERROR); int byteLength = buffer["byteLength"]; ERR_FAIL_COND_V(byteLength < buffer_data.size(), ERR_PARSE_ERROR); - state->buffers.push_back(buffer_data); + p_state->buffers.push_back(buffer_data); } } } - print_verbose("glTF: Total buffers: " + itos(state->buffers.size())); + print_verbose("glTF: Total buffers: " + itos(p_state->buffers.size())); return OK; } -Error GLTFDocument::_encode_buffer_views(Ref<GLTFState> state) { +Error GLTFDocument::_encode_buffer_views(Ref<GLTFState> p_state) { Array buffers; - for (GLTFBufferViewIndex i = 0; i < state->buffer_views.size(); i++) { + for (GLTFBufferViewIndex i = 0; i < p_state->buffer_views.size(); i++) { Dictionary d; - Ref<GLTFBufferView> buffer_view = state->buffer_views[i]; + Ref<GLTFBufferView> buffer_view = p_state->buffer_views[i]; d["buffer"] = buffer_view->buffer; d["byteLength"] = buffer_view->byte_length; @@ -835,19 +828,19 @@ Error GLTFDocument::_encode_buffer_views(Ref<GLTFState> state) { ERR_FAIL_COND_V(!d.has("byteLength"), ERR_INVALID_DATA); buffers.push_back(d); } - print_verbose("glTF: Total buffer views: " + itos(state->buffer_views.size())); + print_verbose("glTF: Total buffer views: " + itos(p_state->buffer_views.size())); if (!buffers.size()) { return OK; } - state->json["bufferViews"] = buffers; + p_state->json["bufferViews"] = buffers; return OK; } -Error GLTFDocument::_parse_buffer_views(Ref<GLTFState> state) { - if (!state->json.has("bufferViews")) { +Error GLTFDocument::_parse_buffer_views(Ref<GLTFState> p_state) { + if (!p_state->json.has("bufferViews")) { return OK; } - const Array &buffers = state->json["bufferViews"]; + const Array &buffers = p_state->json["bufferViews"]; for (GLTFBufferViewIndex i = 0; i < buffers.size(); i++) { const Dictionary &d = buffers[i]; @@ -872,20 +865,20 @@ Error GLTFDocument::_parse_buffer_views(Ref<GLTFState> state) { buffer_view->indices = target == GLTFDocument::ELEMENT_ARRAY_BUFFER; } - state->buffer_views.push_back(buffer_view); + p_state->buffer_views.push_back(buffer_view); } - print_verbose("glTF: Total buffer views: " + itos(state->buffer_views.size())); + print_verbose("glTF: Total buffer views: " + itos(p_state->buffer_views.size())); return OK; } -Error GLTFDocument::_encode_accessors(Ref<GLTFState> state) { +Error GLTFDocument::_encode_accessors(Ref<GLTFState> p_state) { Array accessors; - for (GLTFAccessorIndex i = 0; i < state->accessors.size(); i++) { + for (GLTFAccessorIndex i = 0; i < p_state->accessors.size(); i++) { Dictionary d; - Ref<GLTFAccessor> accessor = state->accessors[i]; + Ref<GLTFAccessor> accessor = p_state->accessors[i]; d["componentType"] = accessor->component_type; d["count"] = accessor->count; d["type"] = _get_accessor_type_name(accessor->type); @@ -931,9 +924,9 @@ Error GLTFDocument::_encode_accessors(Ref<GLTFState> state) { if (!accessors.size()) { return OK; } - state->json["accessors"] = accessors; - ERR_FAIL_COND_V(!state->json.has("accessors"), ERR_FILE_CORRUPT); - print_verbose("glTF: Total accessors: " + itos(state->accessors.size())); + p_state->json["accessors"] = accessors; + ERR_FAIL_COND_V(!p_state->json.has("accessors"), ERR_FILE_CORRUPT); + print_verbose("glTF: Total accessors: " + itos(p_state->accessors.size())); return OK; } @@ -992,11 +985,11 @@ GLTFType GLTFDocument::_get_type_from_str(const String &p_string) { ERR_FAIL_V(GLTFType::TYPE_SCALAR); } -Error GLTFDocument::_parse_accessors(Ref<GLTFState> state) { - if (!state->json.has("accessors")) { +Error GLTFDocument::_parse_accessors(Ref<GLTFState> p_state) { + if (!p_state->json.has("accessors")) { return OK; } - const Array &accessors = state->json["accessors"]; + const Array &accessors = p_state->json["accessors"]; for (GLTFAccessorIndex i = 0; i < accessors.size(); i++) { const Dictionary &d = accessors[i]; @@ -1059,10 +1052,10 @@ Error GLTFDocument::_parse_accessors(Ref<GLTFState> state) { } } - state->accessors.push_back(accessor); + p_state->accessors.push_back(accessor); } - print_verbose("glTF: Total accessors: " + itos(state->accessors.size())); + print_verbose("glTF: Total accessors: " + itos(p_state->accessors.size())); return OK; } @@ -1107,33 +1100,33 @@ String GLTFDocument::_get_type_name(const GLTFType p_component) { return names[p_component]; } -Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> state, const double *src, const int count, const GLTFType type, const int component_type, const bool normalized, const int byte_offset, const bool for_vertex, GLTFBufferViewIndex &r_accessor) { +Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> p_state, const double *p_src, const int p_count, const GLTFType p_type, const int p_component_type, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex, GLTFBufferViewIndex &r_accessor) { const int component_count_for_type[7] = { 1, 2, 3, 4, 4, 9, 16 }; - const int component_count = component_count_for_type[type]; - const int component_size = _get_component_type_size(component_type); + const int component_count = component_count_for_type[p_type]; + const int component_size = _get_component_type_size(p_component_type); ERR_FAIL_COND_V(component_size == 0, FAILED); int skip_every = 0; int skip_bytes = 0; //special case of alignments, as described in spec - switch (component_type) { + switch (p_component_type) { case COMPONENT_TYPE_BYTE: case COMPONENT_TYPE_UNSIGNED_BYTE: { - if (type == TYPE_MAT2) { + if (p_type == TYPE_MAT2) { skip_every = 2; skip_bytes = 2; } - if (type == TYPE_MAT3) { + if (p_type == TYPE_MAT3) { skip_every = 3; skip_bytes = 1; } } break; case COMPONENT_TYPE_SHORT: case COMPONENT_TYPE_UNSIGNED_SHORT: { - if (type == TYPE_MAT3) { + if (p_type == TYPE_MAT3) { skip_every = 6; skip_bytes = 4; } @@ -1144,39 +1137,39 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> state, const double *src, Ref<GLTFBufferView> bv; bv.instantiate(); - const uint32_t offset = bv->byte_offset = byte_offset; - Vector<uint8_t> &gltf_buffer = state->buffers.write[0]; + const uint32_t offset = bv->byte_offset = p_byte_offset; + Vector<uint8_t> &gltf_buffer = p_state->buffers.write[0]; - int stride = _get_component_type_size(component_type); - if (for_vertex && stride % 4) { + int stride = _get_component_type_size(p_component_type); + if (p_for_vertex && stride % 4) { stride += 4 - (stride % 4); //according to spec must be multiple of 4 } //use to debug - print_verbose("glTF: encoding type " + _get_type_name(type) + " component type: " + _get_component_type_name(component_type) + " stride: " + itos(stride) + " amount " + itos(count)); + print_verbose("glTF: encoding type " + _get_type_name(p_type) + " component type: " + _get_component_type_name(p_component_type) + " stride: " + itos(stride) + " amount " + itos(p_count)); - print_verbose("glTF: encoding accessor offset " + itos(byte_offset) + " view offset: " + itos(bv->byte_offset) + " total buffer len: " + itos(gltf_buffer.size()) + " view len " + itos(bv->byte_length)); + print_verbose("glTF: encoding accessor offset " + itos(p_byte_offset) + " view offset: " + itos(bv->byte_offset) + " total buffer len: " + itos(gltf_buffer.size()) + " view len " + itos(bv->byte_length)); - const int buffer_end = (stride * (count - 1)) + _get_component_type_size(component_type); + const int buffer_end = (stride * (p_count - 1)) + _get_component_type_size(p_component_type); // TODO define bv->byte_stride bv->byte_offset = gltf_buffer.size(); - switch (component_type) { + switch (p_component_type) { case COMPONENT_TYPE_BYTE: { Vector<int8_t> buffer; - buffer.resize(count * component_count); + buffer.resize(p_count * component_count); int32_t dst_i = 0; - for (int i = 0; i < count; i++) { + for (int i = 0; i < p_count; i++) { for (int j = 0; j < component_count; j++) { if (skip_every && j > 0 && (j % skip_every) == 0) { dst_i += skip_bytes; } - double d = *src; - if (normalized) { + double d = *p_src; + if (p_normalized) { buffer.write[dst_i] = d * 128.0; } else { buffer.write[dst_i] = d; } - src++; + p_src++; dst_i++; } } @@ -1187,20 +1180,20 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> state, const double *src, } break; case COMPONENT_TYPE_UNSIGNED_BYTE: { Vector<uint8_t> buffer; - buffer.resize(count * component_count); + buffer.resize(p_count * component_count); int32_t dst_i = 0; - for (int i = 0; i < count; i++) { + for (int i = 0; i < p_count; i++) { for (int j = 0; j < component_count; j++) { if (skip_every && j > 0 && (j % skip_every) == 0) { dst_i += skip_bytes; } - double d = *src; - if (normalized) { + double d = *p_src; + if (p_normalized) { buffer.write[dst_i] = d * 255.0; } else { buffer.write[dst_i] = d; } - src++; + p_src++; dst_i++; } } @@ -1209,20 +1202,20 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> state, const double *src, } break; case COMPONENT_TYPE_SHORT: { Vector<int16_t> buffer; - buffer.resize(count * component_count); + buffer.resize(p_count * component_count); int32_t dst_i = 0; - for (int i = 0; i < count; i++) { + for (int i = 0; i < p_count; i++) { for (int j = 0; j < component_count; j++) { if (skip_every && j > 0 && (j % skip_every) == 0) { dst_i += skip_bytes; } - double d = *src; - if (normalized) { + double d = *p_src; + if (p_normalized) { buffer.write[dst_i] = d * 32768.0; } else { buffer.write[dst_i] = d; } - src++; + p_src++; dst_i++; } } @@ -1233,20 +1226,20 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> state, const double *src, } break; case COMPONENT_TYPE_UNSIGNED_SHORT: { Vector<uint16_t> buffer; - buffer.resize(count * component_count); + buffer.resize(p_count * component_count); int32_t dst_i = 0; - for (int i = 0; i < count; i++) { + for (int i = 0; i < p_count; i++) { for (int j = 0; j < component_count; j++) { if (skip_every && j > 0 && (j % skip_every) == 0) { dst_i += skip_bytes; } - double d = *src; - if (normalized) { + double d = *p_src; + if (p_normalized) { buffer.write[dst_i] = d * 65535.0; } else { buffer.write[dst_i] = d; } - src++; + p_src++; dst_i++; } } @@ -1257,16 +1250,16 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> state, const double *src, } break; case COMPONENT_TYPE_INT: { Vector<int> buffer; - buffer.resize(count * component_count); + buffer.resize(p_count * component_count); int32_t dst_i = 0; - for (int i = 0; i < count; i++) { + for (int i = 0; i < p_count; i++) { for (int j = 0; j < component_count; j++) { if (skip_every && j > 0 && (j % skip_every) == 0) { dst_i += skip_bytes; } - double d = *src; + double d = *p_src; buffer.write[dst_i] = d; - src++; + p_src++; dst_i++; } } @@ -1277,16 +1270,16 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> state, const double *src, } break; case COMPONENT_TYPE_FLOAT: { Vector<float> buffer; - buffer.resize(count * component_count); + buffer.resize(p_count * component_count); int32_t dst_i = 0; - for (int i = 0; i < count; i++) { + for (int i = 0; i < p_count; i++) { for (int j = 0; j < component_count; j++) { if (skip_every && j > 0 && (j % skip_every) == 0) { dst_i += skip_bytes; } - double d = *src; + double d = *p_src; buffer.write[dst_i] = d; - src++; + p_src++; dst_i++; } } @@ -1299,53 +1292,53 @@ Error GLTFDocument::_encode_buffer_view(Ref<GLTFState> state, const double *src, ERR_FAIL_COND_V(buffer_end > bv->byte_length, ERR_INVALID_DATA); ERR_FAIL_COND_V((int)(offset + buffer_end) > gltf_buffer.size(), ERR_INVALID_DATA); - r_accessor = bv->buffer = state->buffer_views.size(); - state->buffer_views.push_back(bv); + r_accessor = bv->buffer = p_state->buffer_views.size(); + p_state->buffer_views.push_back(bv); return OK; } -Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> state, double *dst, const GLTFBufferViewIndex p_buffer_view, const int skip_every, const int skip_bytes, const int element_size, const int count, const GLTFType type, const int component_count, const int component_type, const int component_size, const bool normalized, const int byte_offset, const bool for_vertex) { - const Ref<GLTFBufferView> bv = state->buffer_views[p_buffer_view]; +Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, const GLTFBufferViewIndex p_buffer_view, const int p_skip_every, const int p_skip_bytes, const int p_element_size, const int p_count, const GLTFType p_type, const int p_component_count, const int p_component_type, const int p_component_size, const bool p_normalized, const int p_byte_offset, const bool p_for_vertex) { + const Ref<GLTFBufferView> bv = p_state->buffer_views[p_buffer_view]; - int stride = element_size; + int stride = p_element_size; if (bv->byte_stride != -1) { stride = bv->byte_stride; } - if (for_vertex && stride % 4) { + if (p_for_vertex && stride % 4) { stride += 4 - (stride % 4); //according to spec must be multiple of 4 } - ERR_FAIL_INDEX_V(bv->buffer, state->buffers.size(), ERR_PARSE_ERROR); + ERR_FAIL_INDEX_V(bv->buffer, p_state->buffers.size(), ERR_PARSE_ERROR); - const uint32_t offset = bv->byte_offset + byte_offset; - Vector<uint8_t> buffer = state->buffers[bv->buffer]; //copy on write, so no performance hit + const uint32_t offset = bv->byte_offset + p_byte_offset; + Vector<uint8_t> buffer = p_state->buffers[bv->buffer]; //copy on write, so no performance hit const uint8_t *bufptr = buffer.ptr(); //use to debug - print_verbose("glTF: type " + _get_type_name(type) + " component type: " + _get_component_type_name(component_type) + " stride: " + itos(stride) + " amount " + itos(count)); - print_verbose("glTF: accessor offset " + itos(byte_offset) + " view offset: " + itos(bv->byte_offset) + " total buffer len: " + itos(buffer.size()) + " view len " + itos(bv->byte_length)); + print_verbose("glTF: type " + _get_type_name(p_type) + " component type: " + _get_component_type_name(p_component_type) + " stride: " + itos(stride) + " amount " + itos(p_count)); + print_verbose("glTF: accessor offset " + itos(p_byte_offset) + " view offset: " + itos(bv->byte_offset) + " total buffer len: " + itos(buffer.size()) + " view len " + itos(bv->byte_length)); - const int buffer_end = (stride * (count - 1)) + element_size; + const int buffer_end = (stride * (p_count - 1)) + p_element_size; ERR_FAIL_COND_V(buffer_end > bv->byte_length, ERR_PARSE_ERROR); ERR_FAIL_COND_V((int)(offset + buffer_end) > buffer.size(), ERR_PARSE_ERROR); //fill everything as doubles - for (int i = 0; i < count; i++) { + for (int i = 0; i < p_count; i++) { const uint8_t *src = &bufptr[offset + i * stride]; - for (int j = 0; j < component_count; j++) { - if (skip_every && j > 0 && (j % skip_every) == 0) { - src += skip_bytes; + for (int j = 0; j < p_component_count; j++) { + if (p_skip_every && j > 0 && (j % p_skip_every) == 0) { + src += p_skip_bytes; } double d = 0; - switch (component_type) { + switch (p_component_type) { case COMPONENT_TYPE_BYTE: { int8_t b = int8_t(*src); - if (normalized) { + if (p_normalized) { d = (double(b) / 128.0); } else { d = double(b); @@ -1353,7 +1346,7 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> state, double *dst, const } break; case COMPONENT_TYPE_UNSIGNED_BYTE: { uint8_t b = *src; - if (normalized) { + if (p_normalized) { d = (double(b) / 255.0); } else { d = double(b); @@ -1361,7 +1354,7 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> state, double *dst, const } break; case COMPONENT_TYPE_SHORT: { int16_t s = *(int16_t *)src; - if (normalized) { + if (p_normalized) { d = (double(s) / 32768.0); } else { d = double(s); @@ -1369,7 +1362,7 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> state, double *dst, const } break; case COMPONENT_TYPE_UNSIGNED_SHORT: { uint16_t s = *(uint16_t *)src; - if (normalized) { + if (p_normalized) { d = (double(s) / 65535.0); } else { d = double(s); @@ -1383,16 +1376,16 @@ Error GLTFDocument::_decode_buffer_view(Ref<GLTFState> state, double *dst, const } break; } - *dst++ = d; - src += component_size; + *p_dst++ = d; + src += p_component_size; } } return OK; } -int GLTFDocument::_get_component_type_size(const int component_type) { - switch (component_type) { +int GLTFDocument::_get_component_type_size(const int p_component_type) { + switch (p_component_type) { case COMPONENT_TYPE_BYTE: case COMPONENT_TYPE_UNSIGNED_BYTE: return 1; @@ -1412,13 +1405,13 @@ int GLTFDocument::_get_component_type_size(const int component_type) { return 0; } -Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { +Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { //spec, for reference: //https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment - ERR_FAIL_INDEX_V(p_accessor, state->accessors.size(), Vector<double>()); + ERR_FAIL_INDEX_V(p_accessor, p_state->accessors.size(), Vector<double>()); - const Ref<GLTFAccessor> a = state->accessors[p_accessor]; + const Ref<GLTFAccessor> a = p_state->accessors[p_accessor]; const int component_count_for_type[7] = { 1, 2, 3, 4, 4, 9, 16 @@ -1463,9 +1456,9 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> state, const GLTFAc double *dst = dst_buffer.ptrw(); if (a->buffer_view >= 0) { - ERR_FAIL_INDEX_V(a->buffer_view, state->buffer_views.size(), Vector<double>()); + ERR_FAIL_INDEX_V(a->buffer_view, p_state->buffer_views.size(), Vector<double>()); - const Error err = _decode_buffer_view(state, dst, a->buffer_view, skip_every, skip_bytes, element_size, a->count, a->type, component_count, a->component_type, component_size, a->normalized, a->byte_offset, p_for_vertex); + const Error err = _decode_buffer_view(p_state, dst, a->buffer_view, skip_every, skip_bytes, element_size, a->count, a->type, component_count, a->component_type, component_size, a->normalized, a->byte_offset, p_for_vertex); if (err != OK) { return Vector<double>(); } @@ -1482,14 +1475,14 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> state, const GLTFAc indices.resize(a->sparse_count); const int indices_component_size = _get_component_type_size(a->sparse_indices_component_type); - Error err = _decode_buffer_view(state, indices.ptrw(), a->sparse_indices_buffer_view, 0, 0, indices_component_size, a->sparse_count, TYPE_SCALAR, 1, a->sparse_indices_component_type, indices_component_size, false, a->sparse_indices_byte_offset, false); + Error err = _decode_buffer_view(p_state, indices.ptrw(), a->sparse_indices_buffer_view, 0, 0, indices_component_size, a->sparse_count, TYPE_SCALAR, 1, a->sparse_indices_component_type, indices_component_size, false, a->sparse_indices_byte_offset, false); if (err != OK) { return Vector<double>(); } Vector<double> data; data.resize(component_count * a->sparse_count); - err = _decode_buffer_view(state, data.ptrw(), a->sparse_values_buffer_view, skip_every, skip_bytes, element_size, a->sparse_count, a->type, component_count, a->component_type, component_size, a->normalized, a->sparse_values_byte_offset, p_for_vertex); + err = _decode_buffer_view(p_state, data.ptrw(), a->sparse_values_buffer_view, skip_every, skip_bytes, element_size, a->sparse_count, a->type, component_count, a->component_type, component_size, a->normalized, a->sparse_values_byte_offset, p_for_vertex); if (err != OK) { return Vector<double>(); } @@ -1506,7 +1499,7 @@ Vector<double> GLTFDocument::_decode_accessor(Ref<GLTFState> state, const GLTFAc return dst_buffer; } -GLTFAccessorIndex GLTFDocument::_encode_accessor_as_ints(Ref<GLTFState> state, const Vector<int32_t> p_attribs, const bool p_for_vertex) { +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_ints(Ref<GLTFState> p_state, const Vector<int32_t> p_attribs, const bool p_for_vertex) { if (p_attribs.size() == 0) { return -1; } @@ -1539,7 +1532,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_ints(Ref<GLTFState> state, c Ref<GLTFAccessor> accessor; accessor.instantiate(); GLTFBufferIndex buffer_view_i; - int64_t size = state->buffers[0].size(); + int64_t size = p_state->buffers[0].size(); const GLTFType type = GLTFType::TYPE_SCALAR; const int component_type = GLTFDocument::COMPONENT_TYPE_INT; @@ -1550,17 +1543,17 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_ints(Ref<GLTFState> state, c accessor->type = type; accessor->component_type = component_type; accessor->byte_offset = 0; - Error err = _encode_buffer_view(state, attribs.ptr(), attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + Error err = _encode_buffer_view(p_state, attribs.ptr(), attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); if (err != OK) { return -1; } accessor->buffer_view = buffer_view_i; - state->accessors.push_back(accessor); - return state->accessors.size() - 1; + p_state->accessors.push_back(accessor); + return p_state->accessors.size() - 1; } -Vector<int> GLTFDocument::_decode_accessor_as_ints(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); +Vector<int> GLTFDocument::_decode_accessor_as_ints(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex); Vector<int> ret; if (attribs.size() == 0) { @@ -1578,8 +1571,8 @@ Vector<int> GLTFDocument::_decode_accessor_as_ints(Ref<GLTFState> state, const G return ret; } -Vector<float> GLTFDocument::_decode_accessor_as_floats(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); +Vector<float> GLTFDocument::_decode_accessor_as_floats(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex); Vector<float> ret; if (attribs.size() == 0) { @@ -1597,7 +1590,7 @@ Vector<float> GLTFDocument::_decode_accessor_as_floats(Ref<GLTFState> state, con return ret; } -GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec2(Ref<GLTFState> state, const Vector<Vector2> p_attribs, const bool p_for_vertex) { +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec2(Ref<GLTFState> p_state, const Vector<Vector2> p_attribs, const bool p_for_vertex) { if (p_attribs.size() == 0) { return -1; } @@ -1623,7 +1616,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec2(Ref<GLTFState> state, c Ref<GLTFAccessor> accessor; accessor.instantiate(); GLTFBufferIndex buffer_view_i; - int64_t size = state->buffers[0].size(); + int64_t size = p_state->buffers[0].size(); const GLTFType type = GLTFType::TYPE_VEC2; const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; @@ -1634,16 +1627,16 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec2(Ref<GLTFState> state, c accessor->type = type; accessor->component_type = component_type; accessor->byte_offset = 0; - Error err = _encode_buffer_view(state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); if (err != OK) { return -1; } accessor->buffer_view = buffer_view_i; - state->accessors.push_back(accessor); - return state->accessors.size() - 1; + p_state->accessors.push_back(accessor); + return p_state->accessors.size() - 1; } -GLTFAccessorIndex GLTFDocument::_encode_accessor_as_color(Ref<GLTFState> state, const Vector<Color> p_attribs, const bool p_for_vertex) { +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_color(Ref<GLTFState> p_state, const Vector<Color> p_attribs, const bool p_for_vertex) { if (p_attribs.size() == 0) { return -1; } @@ -1672,7 +1665,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_color(Ref<GLTFState> state, Ref<GLTFAccessor> accessor; accessor.instantiate(); GLTFBufferIndex buffer_view_i; - int64_t size = state->buffers[0].size(); + int64_t size = p_state->buffers[0].size(); const GLTFType type = GLTFType::TYPE_VEC4; const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; @@ -1683,31 +1676,31 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_color(Ref<GLTFState> state, accessor->type = type; accessor->component_type = component_type; accessor->byte_offset = 0; - Error err = _encode_buffer_view(state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); if (err != OK) { return -1; } accessor->buffer_view = buffer_view_i; - state->accessors.push_back(accessor); - return state->accessors.size() - 1; + p_state->accessors.push_back(accessor); + return p_state->accessors.size() - 1; } -void GLTFDocument::_calc_accessor_min_max(int i, const int element_count, Vector<double> &type_max, Vector<double> attribs, Vector<double> &type_min) { - if (i == 0) { - for (int32_t type_i = 0; type_i < element_count; type_i++) { - type_max.write[type_i] = attribs[(i * element_count) + type_i]; - type_min.write[type_i] = attribs[(i * element_count) + type_i]; +void GLTFDocument::_calc_accessor_min_max(int p_i, const int p_element_count, Vector<double> &p_type_max, Vector<double> p_attribs, Vector<double> &p_type_min) { + if (p_i == 0) { + for (int32_t type_i = 0; type_i < p_element_count; type_i++) { + p_type_max.write[type_i] = p_attribs[(p_i * p_element_count) + type_i]; + p_type_min.write[type_i] = p_attribs[(p_i * p_element_count) + type_i]; } } - for (int32_t type_i = 0; type_i < element_count; type_i++) { - type_max.write[type_i] = MAX(attribs[(i * element_count) + type_i], type_max[type_i]); - type_min.write[type_i] = MIN(attribs[(i * element_count) + type_i], type_min[type_i]); - type_max.write[type_i] = _filter_number(type_max.write[type_i]); - type_min.write[type_i] = _filter_number(type_min.write[type_i]); + for (int32_t type_i = 0; type_i < p_element_count; type_i++) { + p_type_max.write[type_i] = MAX(p_attribs[(p_i * p_element_count) + type_i], p_type_max[type_i]); + p_type_min.write[type_i] = MIN(p_attribs[(p_i * p_element_count) + type_i], p_type_min[type_i]); + p_type_max.write[type_i] = _filter_number(p_type_max.write[type_i]); + p_type_min.write[type_i] = _filter_number(p_type_min.write[type_i]); } } -GLTFAccessorIndex GLTFDocument::_encode_accessor_as_weights(Ref<GLTFState> state, const Vector<Color> p_attribs, const bool p_for_vertex) { +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_weights(Ref<GLTFState> p_state, const Vector<Color> p_attribs, const bool p_for_vertex) { if (p_attribs.size() == 0) { return -1; } @@ -1737,7 +1730,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_weights(Ref<GLTFState> state Ref<GLTFAccessor> accessor; accessor.instantiate(); GLTFBufferIndex buffer_view_i; - int64_t size = state->buffers[0].size(); + int64_t size = p_state->buffers[0].size(); const GLTFType type = GLTFType::TYPE_VEC4; const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; @@ -1748,16 +1741,16 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_weights(Ref<GLTFState> state accessor->type = type; accessor->component_type = component_type; accessor->byte_offset = 0; - Error err = _encode_buffer_view(state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); if (err != OK) { return -1; } accessor->buffer_view = buffer_view_i; - state->accessors.push_back(accessor); - return state->accessors.size() - 1; + p_state->accessors.push_back(accessor); + return p_state->accessors.size() - 1; } -GLTFAccessorIndex GLTFDocument::_encode_accessor_as_joints(Ref<GLTFState> state, const Vector<Color> p_attribs, const bool p_for_vertex) { +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_joints(Ref<GLTFState> p_state, const Vector<Color> p_attribs, const bool p_for_vertex) { if (p_attribs.size() == 0) { return -1; } @@ -1784,7 +1777,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_joints(Ref<GLTFState> state, Ref<GLTFAccessor> accessor; accessor.instantiate(); GLTFBufferIndex buffer_view_i; - int64_t size = state->buffers[0].size(); + int64_t size = p_state->buffers[0].size(); const GLTFType type = GLTFType::TYPE_VEC4; const int component_type = GLTFDocument::COMPONENT_TYPE_UNSIGNED_SHORT; @@ -1795,16 +1788,16 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_joints(Ref<GLTFState> state, accessor->type = type; accessor->component_type = component_type; accessor->byte_offset = 0; - Error err = _encode_buffer_view(state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); if (err != OK) { return -1; } accessor->buffer_view = buffer_view_i; - state->accessors.push_back(accessor); - return state->accessors.size() - 1; + p_state->accessors.push_back(accessor); + return p_state->accessors.size() - 1; } -GLTFAccessorIndex GLTFDocument::_encode_accessor_as_quaternions(Ref<GLTFState> state, const Vector<Quaternion> p_attribs, const bool p_for_vertex) { +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_quaternions(Ref<GLTFState> p_state, const Vector<Quaternion> p_attribs, const bool p_for_vertex) { if (p_attribs.size() == 0) { return -1; } @@ -1833,7 +1826,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_quaternions(Ref<GLTFState> s Ref<GLTFAccessor> accessor; accessor.instantiate(); GLTFBufferIndex buffer_view_i; - int64_t size = state->buffers[0].size(); + int64_t size = p_state->buffers[0].size(); const GLTFType type = GLTFType::TYPE_VEC4; const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; @@ -1844,17 +1837,17 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_quaternions(Ref<GLTFState> s accessor->type = type; accessor->component_type = component_type; accessor->byte_offset = 0; - Error err = _encode_buffer_view(state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); if (err != OK) { return -1; } accessor->buffer_view = buffer_view_i; - state->accessors.push_back(accessor); - return state->accessors.size() - 1; + p_state->accessors.push_back(accessor); + return p_state->accessors.size() - 1; } -Vector<Vector2> GLTFDocument::_decode_accessor_as_vec2(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); +Vector<Vector2> GLTFDocument::_decode_accessor_as_vec2(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex); Vector<Vector2> ret; if (attribs.size() == 0) { @@ -1873,7 +1866,7 @@ Vector<Vector2> GLTFDocument::_decode_accessor_as_vec2(Ref<GLTFState> state, con return ret; } -GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> state, const Vector<real_t> p_attribs, const bool p_for_vertex) { +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> p_state, const Vector<real_t> p_attribs, const bool p_for_vertex) { if (p_attribs.size() == 0) { return -1; } @@ -1898,7 +1891,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> state, Ref<GLTFAccessor> accessor; accessor.instantiate(); GLTFBufferIndex buffer_view_i; - int64_t size = state->buffers[0].size(); + int64_t size = p_state->buffers[0].size(); const GLTFType type = GLTFType::TYPE_SCALAR; const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; @@ -1909,16 +1902,16 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_floats(Ref<GLTFState> state, accessor->type = type; accessor->component_type = component_type; accessor->byte_offset = 0; - Error err = _encode_buffer_view(state, attribs.ptr(), attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + Error err = _encode_buffer_view(p_state, attribs.ptr(), attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); if (err != OK) { return -1; } accessor->buffer_view = buffer_view_i; - state->accessors.push_back(accessor); - return state->accessors.size() - 1; + p_state->accessors.push_back(accessor); + return p_state->accessors.size() - 1; } -GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec3(Ref<GLTFState> state, const Vector<Vector3> p_attribs, const bool p_for_vertex) { +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec3(Ref<GLTFState> p_state, const Vector<Vector3> p_attribs, const bool p_for_vertex) { if (p_attribs.size() == 0) { return -1; } @@ -1944,7 +1937,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec3(Ref<GLTFState> state, c Ref<GLTFAccessor> accessor; accessor.instantiate(); GLTFBufferIndex buffer_view_i; - int64_t size = state->buffers[0].size(); + int64_t size = p_state->buffers[0].size(); const GLTFType type = GLTFType::TYPE_VEC3; const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; @@ -1955,16 +1948,16 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_vec3(Ref<GLTFState> state, c accessor->type = type; accessor->component_type = component_type; accessor->byte_offset = 0; - Error err = _encode_buffer_view(state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); if (err != OK) { return -1; } accessor->buffer_view = buffer_view_i; - state->accessors.push_back(accessor); - return state->accessors.size() - 1; + p_state->accessors.push_back(accessor); + return p_state->accessors.size() - 1; } -GLTFAccessorIndex GLTFDocument::_encode_accessor_as_xform(Ref<GLTFState> state, const Vector<Transform3D> p_attribs, const bool p_for_vertex) { +GLTFAccessorIndex GLTFDocument::_encode_accessor_as_xform(Ref<GLTFState> p_state, const Vector<Transform3D> p_attribs, const bool p_for_vertex) { if (p_attribs.size() == 0) { return -1; } @@ -2012,7 +2005,7 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_xform(Ref<GLTFState> state, Ref<GLTFAccessor> accessor; accessor.instantiate(); GLTFBufferIndex buffer_view_i; - int64_t size = state->buffers[0].size(); + int64_t size = p_state->buffers[0].size(); const GLTFType type = GLTFType::TYPE_MAT4; const int component_type = GLTFDocument::COMPONENT_TYPE_FLOAT; @@ -2023,17 +2016,17 @@ GLTFAccessorIndex GLTFDocument::_encode_accessor_as_xform(Ref<GLTFState> state, accessor->type = type; accessor->component_type = component_type; accessor->byte_offset = 0; - Error err = _encode_buffer_view(state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); + Error err = _encode_buffer_view(p_state, attribs.ptr(), p_attribs.size(), type, component_type, accessor->normalized, size, p_for_vertex, buffer_view_i); if (err != OK) { return -1; } accessor->buffer_view = buffer_view_i; - state->accessors.push_back(accessor); - return state->accessors.size() - 1; + p_state->accessors.push_back(accessor); + return p_state->accessors.size() - 1; } -Vector<Vector3> GLTFDocument::_decode_accessor_as_vec3(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); +Vector<Vector3> GLTFDocument::_decode_accessor_as_vec3(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex); Vector<Vector3> ret; if (attribs.size() == 0) { @@ -2052,15 +2045,15 @@ Vector<Vector3> GLTFDocument::_decode_accessor_as_vec3(Ref<GLTFState> state, con return ret; } -Vector<Color> GLTFDocument::_decode_accessor_as_color(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); +Vector<Color> GLTFDocument::_decode_accessor_as_color(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex); Vector<Color> ret; if (attribs.size() == 0) { return ret; } - const int type = state->accessors[p_accessor]->type; + const int type = p_state->accessors[p_accessor]->type; ERR_FAIL_COND_V(!(type == TYPE_VEC3 || type == TYPE_VEC4), ret); int vec_len = 3; if (type == TYPE_VEC4) { @@ -2078,8 +2071,8 @@ Vector<Color> GLTFDocument::_decode_accessor_as_color(Ref<GLTFState> state, cons } return ret; } -Vector<Quaternion> GLTFDocument::_decode_accessor_as_quaternion(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); +Vector<Quaternion> GLTFDocument::_decode_accessor_as_quaternion(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex); Vector<Quaternion> ret; if (attribs.size() == 0) { @@ -2097,8 +2090,8 @@ Vector<Quaternion> GLTFDocument::_decode_accessor_as_quaternion(Ref<GLTFState> s } return ret; } -Vector<Transform2D> GLTFDocument::_decode_accessor_as_xform2d(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); +Vector<Transform2D> GLTFDocument::_decode_accessor_as_xform2d(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex); Vector<Transform2D> ret; if (attribs.size() == 0) { @@ -2114,8 +2107,8 @@ Vector<Transform2D> GLTFDocument::_decode_accessor_as_xform2d(Ref<GLTFState> sta return ret; } -Vector<Basis> GLTFDocument::_decode_accessor_as_basis(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); +Vector<Basis> GLTFDocument::_decode_accessor_as_basis(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex); Vector<Basis> ret; if (attribs.size() == 0) { @@ -2132,8 +2125,8 @@ Vector<Basis> GLTFDocument::_decode_accessor_as_basis(Ref<GLTFState> state, cons return ret; } -Vector<Transform3D> GLTFDocument::_decode_accessor_as_xform(Ref<GLTFState> state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { - const Vector<double> attribs = _decode_accessor(state, p_accessor, p_for_vertex); +Vector<Transform3D> GLTFDocument::_decode_accessor_as_xform(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex) { + const Vector<double> attribs = _decode_accessor(p_state, p_accessor, p_for_vertex); Vector<Transform3D> ret; if (attribs.size() == 0) { @@ -2151,15 +2144,15 @@ Vector<Transform3D> GLTFDocument::_decode_accessor_as_xform(Ref<GLTFState> state return ret; } -Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { +Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) { Array meshes; - for (GLTFMeshIndex gltf_mesh_i = 0; gltf_mesh_i < state->meshes.size(); gltf_mesh_i++) { + for (GLTFMeshIndex gltf_mesh_i = 0; gltf_mesh_i < p_state->meshes.size(); gltf_mesh_i++) { print_verbose("glTF: Serializing mesh: " + itos(gltf_mesh_i)); - Ref<ImporterMesh> import_mesh = state->meshes.write[gltf_mesh_i]->get_mesh(); + Ref<ImporterMesh> import_mesh = p_state->meshes.write[gltf_mesh_i]->get_mesh(); if (import_mesh.is_null()) { continue; } - Array instance_materials = state->meshes.write[gltf_mesh_i]->get_instance_materials(); + Array instance_materials = p_state->meshes.write[gltf_mesh_i]->get_instance_materials(); Array primitives; Dictionary gltf_mesh; Array target_names; @@ -2212,7 +2205,7 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { { Vector<Vector3> a = array[Mesh::ARRAY_VERTEX]; ERR_FAIL_COND_V(!a.size(), ERR_INVALID_DATA); - attributes["POSITION"] = _encode_accessor_as_vec3(state, a, true); + attributes["POSITION"] = _encode_accessor_as_vec3(p_state, a, true); vertex_num = a.size(); } { @@ -2229,7 +2222,7 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { out.a = a[(i * 4) + 3]; attribs.write[i] = out; } - attributes["TANGENT"] = _encode_accessor_as_color(state, attribs, true); + attributes["TANGENT"] = _encode_accessor_as_color(p_state, attribs, true); } } { @@ -2241,19 +2234,19 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { for (int i = 0; i < ret_size; i++) { attribs.write[i] = Vector3(a[i]).normalized(); } - attributes["NORMAL"] = _encode_accessor_as_vec3(state, attribs, true); + attributes["NORMAL"] = _encode_accessor_as_vec3(p_state, attribs, true); } } { Vector<Vector2> a = array[Mesh::ARRAY_TEX_UV]; if (a.size()) { - attributes["TEXCOORD_0"] = _encode_accessor_as_vec2(state, a, true); + attributes["TEXCOORD_0"] = _encode_accessor_as_vec2(p_state, a, true); } } { Vector<Vector2> a = array[Mesh::ARRAY_TEX_UV2]; if (a.size()) { - attributes["TEXCOORD_1"] = _encode_accessor_as_vec2(state, a, true); + attributes["TEXCOORD_1"] = _encode_accessor_as_vec2(p_state, a, true); } } for (int custom_i = 0; custom_i < 3; custom_i++) { @@ -2282,7 +2275,7 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { if (!attributes.has(gltf_texcoord_key)) { Vector<Vector2> empty; empty.resize(vertex_num); - attributes[gltf_texcoord_key] = _encode_accessor_as_vec2(state, empty, true); + attributes[gltf_texcoord_key] = _encode_accessor_as_vec2(p_state, empty, true); } } @@ -2303,25 +2296,25 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { } } gltf_texcoord_key = vformat("TEXCOORD_%d", texcoord_i); - attributes[gltf_texcoord_key] = _encode_accessor_as_vec2(state, first_channel, true); + attributes[gltf_texcoord_key] = _encode_accessor_as_vec2(p_state, first_channel, true); gltf_texcoord_key = vformat("TEXCOORD_%d", texcoord_i + 1); - attributes[gltf_texcoord_key] = _encode_accessor_as_vec2(state, second_channel, true); + attributes[gltf_texcoord_key] = _encode_accessor_as_vec2(p_state, second_channel, true); } } { Vector<Color> a = array[Mesh::ARRAY_COLOR]; if (a.size()) { - attributes["COLOR_0"] = _encode_accessor_as_color(state, a, true); + attributes["COLOR_0"] = _encode_accessor_as_color(p_state, a, true); } } HashMap<int, int> joint_i_to_bone_i; - for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); node_i++) { + for (GLTFNodeIndex node_i = 0; node_i < p_state->nodes.size(); node_i++) { GLTFSkinIndex skin_i = -1; - if (state->nodes[node_i]->mesh == gltf_mesh_i) { - skin_i = state->nodes[node_i]->skin; + if (p_state->nodes[node_i]->mesh == gltf_mesh_i) { + skin_i = p_state->nodes[node_i]->skin; } if (skin_i != -1) { - joint_i_to_bone_i = state->skins[skin_i]->joint_i_to_bone_i; + joint_i_to_bone_i = p_state->skins[skin_i]->joint_i_to_bone_i; break; } } @@ -2341,7 +2334,7 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { attribs.write[array_i] = Color(joint_0, joint_1, joint_2, joint_3); } } - attributes["JOINTS_0"] = _encode_accessor_as_joints(state, attribs, true); + attributes["JOINTS_0"] = _encode_accessor_as_joints(p_state, attribs, true); } else if ((a.size() / (JOINT_GROUP_SIZE * 2)) >= vertex_array.size()) { Vector<Color> joints_0; joints_0.resize(vertex_num); @@ -2362,8 +2355,8 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { joint_1.a = a[vertex_i * weights_8_count + 7]; joints_1.write[vertex_i] = joint_1; } - attributes["JOINTS_0"] = _encode_accessor_as_joints(state, joints_0, true); - attributes["JOINTS_1"] = _encode_accessor_as_joints(state, joints_1, true); + attributes["JOINTS_0"] = _encode_accessor_as_joints(p_state, joints_0, true); + attributes["JOINTS_1"] = _encode_accessor_as_joints(p_state, joints_1, true); } } { @@ -2376,7 +2369,7 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { for (int i = 0; i < vertex_count; i++) { attribs.write[i] = Color(a[(i * JOINT_GROUP_SIZE) + 0], a[(i * JOINT_GROUP_SIZE) + 1], a[(i * JOINT_GROUP_SIZE) + 2], a[(i * JOINT_GROUP_SIZE) + 3]); } - attributes["WEIGHTS_0"] = _encode_accessor_as_weights(state, attribs, true); + attributes["WEIGHTS_0"] = _encode_accessor_as_weights(p_state, attribs, true); } else if ((a.size() / (JOINT_GROUP_SIZE * 2)) >= vertex_array.size()) { Vector<Color> weights_0; weights_0.resize(vertex_num); @@ -2397,8 +2390,8 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { weight_1.a = a[vertex_i * weights_8_count + 7]; weights_1.write[vertex_i] = weight_1; } - attributes["WEIGHTS_0"] = _encode_accessor_as_weights(state, weights_0, true); - attributes["WEIGHTS_1"] = _encode_accessor_as_weights(state, weights_1, true); + attributes["WEIGHTS_0"] = _encode_accessor_as_weights(p_state, weights_0, true); + attributes["WEIGHTS_1"] = _encode_accessor_as_weights(p_state, weights_1, true); } } { @@ -2411,7 +2404,7 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { SWAP(mesh_indices.write[k + 0], mesh_indices.write[k + 2]); } } - primitive["indices"] = _encode_accessor_as_ints(state, mesh_indices, true); + primitive["indices"] = _encode_accessor_as_ints(p_state, mesh_indices, true); } else { if (primitive_type == Mesh::PRIMITIVE_TRIANGLES) { //generate indices because they need to be swapped for CW/CCW @@ -2430,7 +2423,7 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { generated_indices.write[k + 2] = k + 1; } } - primitive["indices"] = _encode_accessor_as_ints(state, generated_indices, true); + primitive["indices"] = _encode_accessor_as_ints(p_state, generated_indices, true); } } } @@ -2455,12 +2448,12 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { } } - t["POSITION"] = _encode_accessor_as_vec3(state, varr, true); + t["POSITION"] = _encode_accessor_as_vec3(p_state, varr, true); } Vector<Vector3> narr = array_morph[Mesh::ARRAY_NORMAL]; if (narr.size()) { - t["NORMAL"] = _encode_accessor_as_vec3(state, narr, true); + t["NORMAL"] = _encode_accessor_as_vec3(p_state, narr, true); } Vector<real_t> tarr = array_morph[Mesh::ARRAY_TANGENT]; if (tarr.size()) { @@ -2473,7 +2466,7 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { vec3.y = tarr[(i * 4) + 1]; vec3.z = tarr[(i * 4) + 2]; } - t["TANGENT"] = _encode_accessor_as_vec3(state, attribs, true); + t["TANGENT"] = _encode_accessor_as_vec3(p_state, attribs, true); } targets.push_back(t); } @@ -2483,19 +2476,19 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { if (surface_i < instance_materials.size()) { v = instance_materials.get(surface_i); } - Ref<BaseMaterial3D> mat = v; + Ref<Material> mat = v; if (!mat.is_valid()) { mat = import_mesh->get_surface_material(surface_i); } if (mat.is_valid()) { - HashMap<Ref<BaseMaterial3D>, GLTFMaterialIndex>::Iterator material_cache_i = state->material_cache.find(mat); + HashMap<Ref<Material>, GLTFMaterialIndex>::Iterator material_cache_i = p_state->material_cache.find(mat); if (material_cache_i && material_cache_i->value != -1) { primitive["material"] = material_cache_i->value; } else { - GLTFMaterialIndex mat_i = state->materials.size(); - state->materials.push_back(mat); + GLTFMaterialIndex mat_i = p_state->materials.size(); + p_state->materials.push_back(mat); primitive["material"] = mat_i; - state->material_cache.insert(mat, mat_i); + p_state->material_cache.insert(mat, mat_i); } } @@ -2512,8 +2505,8 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { weights.resize(target_names.size()); for (int name_i = 0; name_i < target_names.size(); name_i++) { real_t weight = 0.0; - if (name_i < state->meshes.write[gltf_mesh_i]->get_blend_weights().size()) { - weight = state->meshes.write[gltf_mesh_i]->get_blend_weights()[name_i]; + if (name_i < p_state->meshes.write[gltf_mesh_i]->get_blend_weights().size()) { + weight = p_state->meshes.write[gltf_mesh_i]->get_blend_weights()[name_i]; } weights[name_i] = weight; } @@ -2533,18 +2526,18 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> state) { if (!meshes.size()) { return OK; } - state->json["meshes"] = meshes; + p_state->json["meshes"] = meshes; print_verbose("glTF: Total meshes: " + itos(meshes.size())); return OK; } -Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { - if (!state->json.has("meshes")) { +Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { + if (!p_state->json.has("meshes")) { return OK; } - Array meshes = state->json["meshes"]; + Array meshes = p_state->json["meshes"]; for (GLTFMeshIndex i = 0; i < meshes.size(); i++) { print_verbose("glTF: Parsing mesh: " + itos(i)); Dictionary d = meshes[i]; @@ -2563,7 +2556,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { if (d.has("name") && !String(d["name"]).is_empty()) { mesh_name = d["name"]; } - import_mesh->set_name(_gen_unique_name(state, vformat("%s_%s", state->scene_name, mesh_name))); + import_mesh->set_name(_gen_unique_name(p_state, vformat("%s_%s", p_state->scene_name, mesh_name))); for (int j = 0; j < primitives.size(); j++) { uint32_t flags = 0; @@ -2599,21 +2592,21 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { ERR_FAIL_COND_V(!a.has("POSITION"), ERR_PARSE_ERROR); int32_t vertex_num = 0; if (a.has("POSITION")) { - PackedVector3Array vertices = _decode_accessor_as_vec3(state, a["POSITION"], true); + PackedVector3Array vertices = _decode_accessor_as_vec3(p_state, a["POSITION"], true); array[Mesh::ARRAY_VERTEX] = vertices; vertex_num = vertices.size(); } if (a.has("NORMAL")) { - array[Mesh::ARRAY_NORMAL] = _decode_accessor_as_vec3(state, a["NORMAL"], true); + array[Mesh::ARRAY_NORMAL] = _decode_accessor_as_vec3(p_state, a["NORMAL"], true); } if (a.has("TANGENT")) { - array[Mesh::ARRAY_TANGENT] = _decode_accessor_as_floats(state, a["TANGENT"], true); + array[Mesh::ARRAY_TANGENT] = _decode_accessor_as_floats(p_state, a["TANGENT"], true); } if (a.has("TEXCOORD_0")) { - array[Mesh::ARRAY_TEX_UV] = _decode_accessor_as_vec2(state, a["TEXCOORD_0"], true); + array[Mesh::ARRAY_TEX_UV] = _decode_accessor_as_vec2(p_state, a["TEXCOORD_0"], true); } if (a.has("TEXCOORD_1")) { - array[Mesh::ARRAY_TEX_UV2] = _decode_accessor_as_vec2(state, a["TEXCOORD_1"], true); + array[Mesh::ARRAY_TEX_UV2] = _decode_accessor_as_vec2(p_state, a["TEXCOORD_1"], true); } for (int custom_i = 0; custom_i < 3; custom_i++) { Vector<float> cur_custom; @@ -2624,12 +2617,12 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { String gltf_texcoord_key = vformat("TEXCOORD_%d", texcoord_i); int num_channels = 0; if (a.has(gltf_texcoord_key)) { - texcoord_first = _decode_accessor_as_vec2(state, a[gltf_texcoord_key], true); + texcoord_first = _decode_accessor_as_vec2(p_state, a[gltf_texcoord_key], true); num_channels = 2; } gltf_texcoord_key = vformat("TEXCOORD_%d", texcoord_i + 1); if (a.has(gltf_texcoord_key)) { - texcoord_second = _decode_accessor_as_vec2(state, a[gltf_texcoord_key], true); + texcoord_second = _decode_accessor_as_vec2(p_state, a[gltf_texcoord_key], true); num_channels = 4; } if (!num_channels) { @@ -2670,14 +2663,14 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { } } if (a.has("COLOR_0")) { - array[Mesh::ARRAY_COLOR] = _decode_accessor_as_color(state, a["COLOR_0"], true); + array[Mesh::ARRAY_COLOR] = _decode_accessor_as_color(p_state, a["COLOR_0"], true); has_vertex_color = true; } if (a.has("JOINTS_0") && !a.has("JOINTS_1")) { - array[Mesh::ARRAY_BONES] = _decode_accessor_as_ints(state, a["JOINTS_0"], true); + array[Mesh::ARRAY_BONES] = _decode_accessor_as_ints(p_state, a["JOINTS_0"], true); } else if (a.has("JOINTS_0") && a.has("JOINTS_1")) { - PackedInt32Array joints_0 = _decode_accessor_as_ints(state, a["JOINTS_0"], true); - PackedInt32Array joints_1 = _decode_accessor_as_ints(state, a["JOINTS_1"], true); + PackedInt32Array joints_0 = _decode_accessor_as_ints(p_state, a["JOINTS_0"], true); + PackedInt32Array joints_1 = _decode_accessor_as_ints(p_state, a["JOINTS_1"], true); ERR_FAIL_COND_V(joints_0.size() != joints_1.size(), ERR_INVALID_DATA); int32_t weight_8_count = JOINT_GROUP_SIZE * 2; Vector<int> joints; @@ -2695,7 +2688,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { array[Mesh::ARRAY_BONES] = joints; } if (a.has("WEIGHTS_0") && !a.has("WEIGHTS_1")) { - Vector<float> weights = _decode_accessor_as_floats(state, a["WEIGHTS_0"], true); + Vector<float> weights = _decode_accessor_as_floats(p_state, a["WEIGHTS_0"], true); { //gltf does not seem to normalize the weights for some reason.. int wc = weights.size(); float *w = weights.ptrw(); @@ -2716,8 +2709,8 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { } array[Mesh::ARRAY_WEIGHTS] = weights; } else if (a.has("WEIGHTS_0") && a.has("WEIGHTS_1")) { - Vector<float> weights_0 = _decode_accessor_as_floats(state, a["WEIGHTS_0"], true); - Vector<float> weights_1 = _decode_accessor_as_floats(state, a["WEIGHTS_1"], true); + Vector<float> weights_0 = _decode_accessor_as_floats(p_state, a["WEIGHTS_0"], true); + Vector<float> weights_1 = _decode_accessor_as_floats(p_state, a["WEIGHTS_1"], true); Vector<float> weights; ERR_FAIL_COND_V(weights_0.size() != weights_1.size(), ERR_INVALID_DATA); int32_t weight_8_count = JOINT_GROUP_SIZE * 2; @@ -2762,7 +2755,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { } if (p.has("indices")) { - Vector<int> indices = _decode_accessor_as_ints(state, p["indices"], false); + Vector<int> indices = _decode_accessor_as_ints(p_state, p["indices"], false); if (primitive == Mesh::PRIMITIVE_TRIANGLES) { //swap around indices, convert ccw to cw for front face @@ -2836,7 +2829,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { } if (t.has("POSITION")) { - Vector<Vector3> varr = _decode_accessor_as_vec3(state, t["POSITION"], true); + Vector<Vector3> varr = _decode_accessor_as_vec3(p_state, t["POSITION"], true); const Vector<Vector3> src_varr = array[Mesh::ARRAY_VERTEX]; const int size = src_varr.size(); ERR_FAIL_COND_V(size == 0, ERR_PARSE_ERROR); @@ -2858,7 +2851,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { array_copy[Mesh::ARRAY_VERTEX] = varr; } if (t.has("NORMAL")) { - Vector<Vector3> narr = _decode_accessor_as_vec3(state, t["NORMAL"], true); + Vector<Vector3> narr = _decode_accessor_as_vec3(p_state, t["NORMAL"], true); const Vector<Vector3> src_narr = array[Mesh::ARRAY_NORMAL]; int size = src_narr.size(); ERR_FAIL_COND_V(size == 0, ERR_PARSE_ERROR); @@ -2880,7 +2873,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { array_copy[Mesh::ARRAY_NORMAL] = narr; } if (t.has("TANGENT")) { - const Vector<Vector3> tangents_v3 = _decode_accessor_as_vec3(state, t["TANGENT"], true); + const Vector<Vector3> tangents_v3 = _decode_accessor_as_vec3(p_state, t["TANGENT"], true); const Vector<float> src_tangents = array[Mesh::ARRAY_TANGENT]; ERR_FAIL_COND_V(src_tangents.size() == 0, ERR_PARSE_ERROR); @@ -2936,16 +2929,18 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { } } - Ref<BaseMaterial3D> mat; + Ref<Material> mat; String mat_name; - if (!state->discard_meshes_and_materials) { + if (!p_state->discard_meshes_and_materials) { if (p.has("material")) { const int material = p["material"]; - ERR_FAIL_INDEX_V(material, state->materials.size(), ERR_FILE_CORRUPT); - Ref<BaseMaterial3D> mat3d = state->materials[material]; + ERR_FAIL_INDEX_V(material, p_state->materials.size(), ERR_FILE_CORRUPT); + Ref<Material> mat3d = p_state->materials[material]; ERR_FAIL_NULL_V(mat3d, ERR_FILE_CORRUPT); - if (has_vertex_color) { - mat3d->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + + Ref<BaseMaterial3D> base_material = mat3d; + if (has_vertex_color && base_material.is_valid()) { + base_material->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); } mat = mat3d; @@ -2953,7 +2948,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { Ref<StandardMaterial3D> mat3d; mat3d.instantiate(); if (has_vertex_color) { - mat3d->set_flag(BaseMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + mat3d->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); } mat = mat3d; } @@ -2982,22 +2977,22 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) { mesh->set_blend_weights(blend_weights); mesh->set_mesh(import_mesh); - state->meshes.push_back(mesh); + p_state->meshes.push_back(mesh); } - print_verbose("glTF: Total meshes: " + itos(state->meshes.size())); + print_verbose("glTF: Total meshes: " + itos(p_state->meshes.size())); return OK; } -Error GLTFDocument::_serialize_images(Ref<GLTFState> state, const String &p_path) { +Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state, const String &p_path) { Array images; - for (int i = 0; i < state->images.size(); i++) { + for (int i = 0; i < p_state->images.size(); i++) { Dictionary d; - ERR_CONTINUE(state->images[i].is_null()); + ERR_CONTINUE(p_state->images[i].is_null()); - Ref<Image> image = state->images[i]->get_image(); + Ref<Image> image = p_state->images[i]->get_image(); ERR_CONTINUE(image.is_null()); if (p_path.to_lower().ends_with("glb") || p_path.is_empty()) { @@ -3008,8 +3003,8 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> state, const String &p_path const GLTFBufferIndex bi = 0; bv->buffer = bi; - bv->byte_offset = state->buffers[bi].size(); - ERR_FAIL_INDEX_V(bi, state->buffers.size(), ERR_PARAMETER_RANGE_ERROR); + bv->byte_offset = p_state->buffers[bi].size(); + ERR_FAIL_INDEX_V(bi, p_state->buffers.size(), ERR_PARAMETER_RANGE_ERROR); Vector<uint8_t> buffer; Ref<ImageTexture> img_tex = image; @@ -3020,21 +3015,21 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> state, const String &p_path ERR_FAIL_COND_V_MSG(err, err, "Can't convert image to PNG."); bv->byte_length = buffer.size(); - state->buffers.write[bi].resize(state->buffers[bi].size() + bv->byte_length); - memcpy(&state->buffers.write[bi].write[bv->byte_offset], buffer.ptr(), buffer.size()); - ERR_FAIL_COND_V(bv->byte_offset + bv->byte_length > state->buffers[bi].size(), ERR_FILE_CORRUPT); + p_state->buffers.write[bi].resize(p_state->buffers[bi].size() + bv->byte_length); + memcpy(&p_state->buffers.write[bi].write[bv->byte_offset], buffer.ptr(), buffer.size()); + ERR_FAIL_COND_V(bv->byte_offset + bv->byte_length > p_state->buffers[bi].size(), ERR_FILE_CORRUPT); - state->buffer_views.push_back(bv); - bvi = state->buffer_views.size() - 1; + p_state->buffer_views.push_back(bv); + bvi = p_state->buffer_views.size() - 1; d["bufferView"] = bvi; d["mimeType"] = "image/png"; } else { ERR_FAIL_COND_V(p_path.is_empty(), ERR_INVALID_PARAMETER); - String img_name = state->images[i]->get_name(); + String img_name = p_state->images[i]->get_name(); if (img_name.is_empty()) { img_name = itos(i); } - img_name = _gen_unique_name(state, img_name); + img_name = _gen_unique_name(p_state, img_name); img_name = img_name.pad_zeros(3) + ".png"; String texture_dir = "textures"; String path = p_path.get_base_dir(); @@ -3049,25 +3044,25 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> state, const String &p_path images.push_back(d); } - print_verbose("Total images: " + itos(state->images.size())); + print_verbose("Total images: " + itos(p_state->images.size())); if (!images.size()) { return OK; } - state->json["images"] = images; + p_state->json["images"] = images; return OK; } -Error GLTFDocument::_parse_images(Ref<GLTFState> state, const String &p_base_path) { - ERR_FAIL_NULL_V(state, ERR_INVALID_PARAMETER); - if (!state->json.has("images")) { +Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_path) { + ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + if (!p_state->json.has("images")) { return OK; } // Ref: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#images - const Array &images = state->json["images"]; + const Array &images = p_state->json["images"]; for (int i = 0; i < images.size(); i++) { const Dictionary &d = images[i]; @@ -3105,7 +3100,7 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> state, const String &p_base_pat !uri.begins_with("data:image/png;base64") && !uri.begins_with("data:image/jpeg;base64")) { WARN_PRINT(vformat("glTF: Image index '%d' uses an unsupported URI data type: %s. Skipping it.", i, uri)); - state->images.push_back(Ref<Texture2D>()); // Placeholder to keep count. + p_state->images.push_back(Ref<Texture2D>()); // Placeholder to keep count. continue; } data = _parse_base64_uri(uri); @@ -3130,23 +3125,23 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> state, const String &p_base_pat // the material), so we do this only as fallback. Ref<Texture2D> texture = ResourceLoader::load(uri); if (texture.is_valid()) { - state->images.push_back(texture); + p_state->images.push_back(texture); continue; } else if (mimetype == "image/png" || mimetype == "image/jpeg") { // Fallback to loading as byte array. // This enables us to support the spec's requirement that we honor mimetype // regardless of file URI. - data = FileAccess::get_file_as_array(uri); + data = FileAccess::get_file_as_bytes(uri); if (data.size() == 0) { WARN_PRINT(vformat("glTF: Image index '%d' couldn't be loaded as a buffer of MIME type '%s' from URI: %s. Skipping it.", i, mimetype, uri)); - state->images.push_back(Ref<Texture2D>()); // Placeholder to keep count. + p_state->images.push_back(Ref<Texture2D>()); // Placeholder to keep count. continue; } data_ptr = data.ptr(); data_size = data.size(); } else { WARN_PRINT(vformat("glTF: Image index '%d' couldn't be loaded from URI: %s. Skipping it.", i, uri)); - state->images.push_back(Ref<Texture2D>()); // Placeholder to keep count. + p_state->images.push_back(Ref<Texture2D>()); // Placeholder to keep count. continue; } } @@ -3157,16 +3152,16 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> state, const String &p_base_pat const GLTFBufferViewIndex bvi = d["bufferView"]; - ERR_FAIL_INDEX_V(bvi, state->buffer_views.size(), ERR_PARAMETER_RANGE_ERROR); + ERR_FAIL_INDEX_V(bvi, p_state->buffer_views.size(), ERR_PARAMETER_RANGE_ERROR); - Ref<GLTFBufferView> bv = state->buffer_views[bvi]; + Ref<GLTFBufferView> bv = p_state->buffer_views[bvi]; const GLTFBufferIndex bi = bv->buffer; - ERR_FAIL_INDEX_V(bi, state->buffers.size(), ERR_PARAMETER_RANGE_ERROR); + ERR_FAIL_INDEX_V(bi, p_state->buffers.size(), ERR_PARAMETER_RANGE_ERROR); - ERR_FAIL_COND_V(bv->byte_offset + bv->byte_length > state->buffers[bi].size(), ERR_FILE_CORRUPT); + ERR_FAIL_COND_V(bv->byte_offset + bv->byte_length > p_state->buffers[bi].size(), ERR_FILE_CORRUPT); - data_ptr = &state->buffers[bi][bv->byte_offset]; + data_ptr = &p_state->buffers[bi][bv->byte_offset]; data_size = bv->byte_length; } @@ -3199,26 +3194,26 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> state, const String &p_base_pat // Now we've done our best, fix your scenes. if (img.is_null()) { ERR_PRINT(vformat("glTF: Couldn't load image index '%d' with its given mimetype: %s.", i, mimetype)); - state->images.push_back(Ref<Texture2D>()); + p_state->images.push_back(Ref<Texture2D>()); continue; } - state->images.push_back(ImageTexture::create_from_image(img)); + p_state->images.push_back(ImageTexture::create_from_image(img)); } - print_verbose("glTF: Total images: " + itos(state->images.size())); + print_verbose("glTF: Total images: " + itos(p_state->images.size())); return OK; } -Error GLTFDocument::_serialize_textures(Ref<GLTFState> state) { - if (!state->textures.size()) { +Error GLTFDocument::_serialize_textures(Ref<GLTFState> p_state) { + if (!p_state->textures.size()) { return OK; } Array textures; - for (int32_t i = 0; i < state->textures.size(); i++) { + for (int32_t i = 0; i < p_state->textures.size(); i++) { Dictionary d; - Ref<GLTFTexture> t = state->textures[i]; + Ref<GLTFTexture> t = p_state->textures[i]; ERR_CONTINUE(t->get_src_image() == -1); d["source"] = t->get_src_image(); @@ -3228,17 +3223,17 @@ Error GLTFDocument::_serialize_textures(Ref<GLTFState> state) { } textures.push_back(d); } - state->json["textures"] = textures; + p_state->json["textures"] = textures; return OK; } -Error GLTFDocument::_parse_textures(Ref<GLTFState> state) { - if (!state->json.has("textures")) { +Error GLTFDocument::_parse_textures(Ref<GLTFState> p_state) { + if (!p_state->json.has("textures")) { return OK; } - const Array &textures = state->json["textures"]; + const Array &textures = p_state->json["textures"]; for (GLTFTextureIndex i = 0; i < textures.size(); i++) { const Dictionary &d = textures[i]; @@ -3252,96 +3247,96 @@ Error GLTFDocument::_parse_textures(Ref<GLTFState> state) { } else { t->set_sampler(-1); } - state->textures.push_back(t); + p_state->textures.push_back(t); } return OK; } -GLTFTextureIndex GLTFDocument::_set_texture(Ref<GLTFState> state, Ref<Texture2D> p_texture, StandardMaterial3D::TextureFilter p_filter_mode, bool p_repeats) { +GLTFTextureIndex GLTFDocument::_set_texture(Ref<GLTFState> p_state, Ref<Texture2D> p_texture, StandardMaterial3D::TextureFilter p_filter_mode, bool p_repeats) { ERR_FAIL_COND_V(p_texture.is_null(), -1); Ref<GLTFTexture> gltf_texture; gltf_texture.instantiate(); ERR_FAIL_COND_V(p_texture->get_image().is_null(), -1); - GLTFImageIndex gltf_src_image_i = state->images.size(); - state->images.push_back(p_texture); + GLTFImageIndex gltf_src_image_i = p_state->images.size(); + p_state->images.push_back(p_texture); gltf_texture->set_src_image(gltf_src_image_i); - gltf_texture->set_sampler(_set_sampler_for_mode(state, p_filter_mode, p_repeats)); - GLTFTextureIndex gltf_texture_i = state->textures.size(); - state->textures.push_back(gltf_texture); + gltf_texture->set_sampler(_set_sampler_for_mode(p_state, p_filter_mode, p_repeats)); + GLTFTextureIndex gltf_texture_i = p_state->textures.size(); + p_state->textures.push_back(gltf_texture); return gltf_texture_i; } -Ref<Texture2D> GLTFDocument::_get_texture(Ref<GLTFState> state, const GLTFTextureIndex p_texture) { - ERR_FAIL_INDEX_V(p_texture, state->textures.size(), Ref<Texture2D>()); - const GLTFImageIndex image = state->textures[p_texture]->get_src_image(); +Ref<Texture2D> GLTFDocument::_get_texture(Ref<GLTFState> p_state, const GLTFTextureIndex p_texture) { + ERR_FAIL_INDEX_V(p_texture, p_state->textures.size(), Ref<Texture2D>()); + const GLTFImageIndex image = p_state->textures[p_texture]->get_src_image(); - ERR_FAIL_INDEX_V(image, state->images.size(), Ref<Texture2D>()); + ERR_FAIL_INDEX_V(image, p_state->images.size(), Ref<Texture2D>()); - return state->images[image]; + return p_state->images[image]; } -GLTFTextureSamplerIndex GLTFDocument::_set_sampler_for_mode(Ref<GLTFState> state, StandardMaterial3D::TextureFilter p_filter_mode, bool p_repeats) { - for (int i = 0; i < state->texture_samplers.size(); ++i) { - if (state->texture_samplers[i]->get_filter_mode() == p_filter_mode) { +GLTFTextureSamplerIndex GLTFDocument::_set_sampler_for_mode(Ref<GLTFState> p_state, StandardMaterial3D::TextureFilter p_filter_mode, bool p_repeats) { + for (int i = 0; i < p_state->texture_samplers.size(); ++i) { + if (p_state->texture_samplers[i]->get_filter_mode() == p_filter_mode) { return i; } } - GLTFTextureSamplerIndex gltf_sampler_i = state->texture_samplers.size(); + GLTFTextureSamplerIndex gltf_sampler_i = p_state->texture_samplers.size(); Ref<GLTFTextureSampler> gltf_sampler; gltf_sampler.instantiate(); gltf_sampler->set_filter_mode(p_filter_mode); gltf_sampler->set_wrap_mode(p_repeats); - state->texture_samplers.push_back(gltf_sampler); + p_state->texture_samplers.push_back(gltf_sampler); return gltf_sampler_i; } -Ref<GLTFTextureSampler> GLTFDocument::_get_sampler_for_texture(Ref<GLTFState> state, const GLTFTextureIndex p_texture) { - ERR_FAIL_INDEX_V(p_texture, state->textures.size(), Ref<Texture2D>()); - const GLTFTextureSamplerIndex sampler = state->textures[p_texture]->get_sampler(); +Ref<GLTFTextureSampler> GLTFDocument::_get_sampler_for_texture(Ref<GLTFState> p_state, const GLTFTextureIndex p_texture) { + ERR_FAIL_INDEX_V(p_texture, p_state->textures.size(), Ref<Texture2D>()); + const GLTFTextureSamplerIndex sampler = p_state->textures[p_texture]->get_sampler(); if (sampler == -1) { - return state->default_texture_sampler; + return p_state->default_texture_sampler; } else { - ERR_FAIL_INDEX_V(sampler, state->texture_samplers.size(), Ref<GLTFTextureSampler>()); + ERR_FAIL_INDEX_V(sampler, p_state->texture_samplers.size(), Ref<GLTFTextureSampler>()); - return state->texture_samplers[sampler]; + return p_state->texture_samplers[sampler]; } } -Error GLTFDocument::_serialize_texture_samplers(Ref<GLTFState> state) { - if (!state->texture_samplers.size()) { +Error GLTFDocument::_serialize_texture_samplers(Ref<GLTFState> p_state) { + if (!p_state->texture_samplers.size()) { return OK; } Array samplers; - for (int32_t i = 0; i < state->texture_samplers.size(); ++i) { + for (int32_t i = 0; i < p_state->texture_samplers.size(); ++i) { Dictionary d; - Ref<GLTFTextureSampler> s = state->texture_samplers[i]; + Ref<GLTFTextureSampler> s = p_state->texture_samplers[i]; d["magFilter"] = s->get_mag_filter(); d["minFilter"] = s->get_min_filter(); d["wrapS"] = s->get_wrap_s(); d["wrapT"] = s->get_wrap_t(); samplers.push_back(d); } - state->json["samplers"] = samplers; + p_state->json["samplers"] = samplers; return OK; } -Error GLTFDocument::_parse_texture_samplers(Ref<GLTFState> state) { - state->default_texture_sampler.instantiate(); - state->default_texture_sampler->set_min_filter(GLTFTextureSampler::FilterMode::LINEAR_MIPMAP_LINEAR); - state->default_texture_sampler->set_mag_filter(GLTFTextureSampler::FilterMode::LINEAR); - state->default_texture_sampler->set_wrap_s(GLTFTextureSampler::WrapMode::REPEAT); - state->default_texture_sampler->set_wrap_t(GLTFTextureSampler::WrapMode::REPEAT); +Error GLTFDocument::_parse_texture_samplers(Ref<GLTFState> p_state) { + p_state->default_texture_sampler.instantiate(); + p_state->default_texture_sampler->set_min_filter(GLTFTextureSampler::FilterMode::LINEAR_MIPMAP_LINEAR); + p_state->default_texture_sampler->set_mag_filter(GLTFTextureSampler::FilterMode::LINEAR); + p_state->default_texture_sampler->set_wrap_s(GLTFTextureSampler::WrapMode::REPEAT); + p_state->default_texture_sampler->set_wrap_t(GLTFTextureSampler::WrapMode::REPEAT); - if (!state->json.has("samplers")) { + if (!p_state->json.has("samplers")) { return OK; } - const Array &samplers = state->json["samplers"]; + const Array &samplers = p_state->json["samplers"]; for (int i = 0; i < samplers.size(); ++i) { const Dictionary &d = samplers[i]; @@ -3371,30 +3366,30 @@ Error GLTFDocument::_parse_texture_samplers(Ref<GLTFState> state) { sampler->set_wrap_t(GLTFTextureSampler::WrapMode::DEFAULT); } - state->texture_samplers.push_back(sampler); + p_state->texture_samplers.push_back(sampler); } return OK; } -Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) { +Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) { Array materials; - for (int32_t i = 0; i < state->materials.size(); i++) { + for (int32_t i = 0; i < p_state->materials.size(); i++) { Dictionary d; - - Ref<BaseMaterial3D> material = state->materials[i]; + Ref<Material> material = p_state->materials[i]; if (material.is_null()) { materials.push_back(d); continue; } if (!material->get_name().is_empty()) { - d["name"] = _gen_unique_name(state, material->get_name()); + d["name"] = _gen_unique_name(p_state, material->get_name()); } - { + Ref<BaseMaterial3D> base_material = material; + if (base_material.is_valid()) { Dictionary mr; { Array arr; - const Color c = material->get_albedo().srgb_to_linear(); + const Color c = base_material->get_albedo().srgb_to_linear(); arr.push_back(c.r); arr.push_back(c.g); arr.push_back(c.b); @@ -3403,167 +3398,169 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) { } { Dictionary bct; - Ref<Texture2D> albedo_texture = material->get_texture(BaseMaterial3D::TEXTURE_ALBEDO); - GLTFTextureIndex gltf_texture_index = -1; + if (base_material.is_valid()) { + Ref<Texture2D> albedo_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_ALBEDO); + GLTFTextureIndex gltf_texture_index = -1; - if (albedo_texture.is_valid() && albedo_texture->get_image().is_valid()) { - albedo_texture->set_name(material->get_name() + "_albedo"); - gltf_texture_index = _set_texture(state, albedo_texture, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT)); - } - if (gltf_texture_index != -1) { - bct["index"] = gltf_texture_index; - Dictionary extensions = _serialize_texture_transform_uv1(material); - if (!extensions.is_empty()) { - bct["extensions"] = extensions; - state->use_khr_texture_transform = true; + if (albedo_texture.is_valid() && albedo_texture->get_image().is_valid()) { + albedo_texture->set_name(material->get_name() + "_albedo"); + gltf_texture_index = _set_texture(p_state, albedo_texture, base_material->get_texture_filter(), base_material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT)); + } + if (gltf_texture_index != -1) { + bct["index"] = gltf_texture_index; + Dictionary extensions = _serialize_texture_transform_uv1(material); + if (!extensions.is_empty()) { + bct["extensions"] = extensions; + p_state->use_khr_texture_transform = true; + } + mr["baseColorTexture"] = bct; } - mr["baseColorTexture"] = bct; } } - - mr["metallicFactor"] = material->get_metallic(); - mr["roughnessFactor"] = material->get_roughness(); - bool has_roughness = material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS).is_valid() && material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS)->get_image().is_valid(); - bool has_ao = material->get_feature(BaseMaterial3D::FEATURE_AMBIENT_OCCLUSION) && material->get_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION).is_valid(); - bool has_metalness = material->get_texture(BaseMaterial3D::TEXTURE_METALLIC).is_valid() && material->get_texture(BaseMaterial3D::TEXTURE_METALLIC)->get_image().is_valid(); - if (has_ao || has_roughness || has_metalness) { - Dictionary mrt; - Ref<Texture2D> roughness_texture = material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS); - BaseMaterial3D::TextureChannel roughness_channel = material->get_roughness_texture_channel(); - Ref<Texture2D> metallic_texture = material->get_texture(BaseMaterial3D::TEXTURE_METALLIC); - BaseMaterial3D::TextureChannel metalness_channel = material->get_metallic_texture_channel(); - Ref<Texture2D> ao_texture = material->get_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION); - BaseMaterial3D::TextureChannel ao_channel = material->get_ao_texture_channel(); - Ref<ImageTexture> orm_texture; - orm_texture.instantiate(); - Ref<Image> orm_image; - orm_image.instantiate(); - int32_t height = 0; - int32_t width = 0; - Ref<Image> ao_image; - if (has_ao) { - height = ao_texture->get_height(); - width = ao_texture->get_width(); - ao_image = ao_texture->get_image(); - Ref<ImageTexture> img_tex = ao_image; - if (img_tex.is_valid()) { - ao_image = img_tex->get_image(); + if (base_material.is_valid()) { + mr["metallicFactor"] = base_material->get_metallic(); + mr["roughnessFactor"] = base_material->get_roughness(); + bool has_roughness = base_material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS).is_valid() && base_material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS)->get_image().is_valid(); + bool has_ao = base_material->get_feature(BaseMaterial3D::FEATURE_AMBIENT_OCCLUSION) && base_material->get_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION).is_valid(); + bool has_metalness = base_material->get_texture(BaseMaterial3D::TEXTURE_METALLIC).is_valid() && base_material->get_texture(BaseMaterial3D::TEXTURE_METALLIC)->get_image().is_valid(); + if (has_ao || has_roughness || has_metalness) { + Dictionary mrt; + Ref<Texture2D> roughness_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_ROUGHNESS); + BaseMaterial3D::TextureChannel roughness_channel = base_material->get_roughness_texture_channel(); + Ref<Texture2D> metallic_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_METALLIC); + BaseMaterial3D::TextureChannel metalness_channel = base_material->get_metallic_texture_channel(); + Ref<Texture2D> ao_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION); + BaseMaterial3D::TextureChannel ao_channel = base_material->get_ao_texture_channel(); + Ref<ImageTexture> orm_texture; + orm_texture.instantiate(); + Ref<Image> orm_image; + orm_image.instantiate(); + int32_t height = 0; + int32_t width = 0; + Ref<Image> ao_image; + if (has_ao) { + height = ao_texture->get_height(); + width = ao_texture->get_width(); + ao_image = ao_texture->get_image(); + Ref<ImageTexture> img_tex = ao_image; + if (img_tex.is_valid()) { + ao_image = img_tex->get_image(); + } + if (ao_image->is_compressed()) { + ao_image->decompress(); + } } - if (ao_image->is_compressed()) { - ao_image->decompress(); + Ref<Image> roughness_image; + if (has_roughness) { + height = roughness_texture->get_height(); + width = roughness_texture->get_width(); + roughness_image = roughness_texture->get_image(); + Ref<ImageTexture> img_tex = roughness_image; + if (img_tex.is_valid()) { + roughness_image = img_tex->get_image(); + } + if (roughness_image->is_compressed()) { + roughness_image->decompress(); + } } - } - Ref<Image> roughness_image; - if (has_roughness) { - height = roughness_texture->get_height(); - width = roughness_texture->get_width(); - roughness_image = roughness_texture->get_image(); - Ref<ImageTexture> img_tex = roughness_image; - if (img_tex.is_valid()) { - roughness_image = img_tex->get_image(); + Ref<Image> metallness_image; + if (has_metalness) { + height = metallic_texture->get_height(); + width = metallic_texture->get_width(); + metallness_image = metallic_texture->get_image(); + Ref<ImageTexture> img_tex = metallness_image; + if (img_tex.is_valid()) { + metallness_image = img_tex->get_image(); + } + if (metallness_image->is_compressed()) { + metallness_image->decompress(); + } } - if (roughness_image->is_compressed()) { - roughness_image->decompress(); + Ref<Texture2D> albedo_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_ALBEDO); + if (albedo_texture.is_valid() && albedo_texture->get_image().is_valid()) { + height = albedo_texture->get_height(); + width = albedo_texture->get_width(); } - } - Ref<Image> metallness_image; - if (has_metalness) { - height = metallic_texture->get_height(); - width = metallic_texture->get_width(); - metallness_image = metallic_texture->get_image(); - Ref<ImageTexture> img_tex = metallness_image; - if (img_tex.is_valid()) { - metallness_image = img_tex->get_image(); + orm_image->initialize_data(width, height, false, Image::FORMAT_RGBA8); + if (ao_image.is_valid() && ao_image->get_size() != Vector2(width, height)) { + ao_image->resize(width, height, Image::INTERPOLATE_LANCZOS); } - if (metallness_image->is_compressed()) { - metallness_image->decompress(); + if (roughness_image.is_valid() && roughness_image->get_size() != Vector2(width, height)) { + roughness_image->resize(width, height, Image::INTERPOLATE_LANCZOS); } - } - Ref<Texture2D> albedo_texture = material->get_texture(BaseMaterial3D::TEXTURE_ALBEDO); - if (albedo_texture.is_valid() && albedo_texture->get_image().is_valid()) { - height = albedo_texture->get_height(); - width = albedo_texture->get_width(); - } - orm_image->initialize_data(width, height, false, Image::FORMAT_RGBA8); - if (ao_image.is_valid() && ao_image->get_size() != Vector2(width, height)) { - ao_image->resize(width, height, Image::INTERPOLATE_LANCZOS); - } - if (roughness_image.is_valid() && roughness_image->get_size() != Vector2(width, height)) { - roughness_image->resize(width, height, Image::INTERPOLATE_LANCZOS); - } - if (metallness_image.is_valid() && metallness_image->get_size() != Vector2(width, height)) { - metallness_image->resize(width, height, Image::INTERPOLATE_LANCZOS); - } - for (int32_t h = 0; h < height; h++) { - for (int32_t w = 0; w < width; w++) { - Color c = Color(1.0f, 1.0f, 1.0f); - if (has_ao) { - if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == ao_channel) { - c.r = ao_image->get_pixel(w, h).r; - } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == ao_channel) { - c.r = ao_image->get_pixel(w, h).g; - } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == ao_channel) { - c.r = ao_image->get_pixel(w, h).b; - } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == ao_channel) { - c.r = ao_image->get_pixel(w, h).a; + if (metallness_image.is_valid() && metallness_image->get_size() != Vector2(width, height)) { + metallness_image->resize(width, height, Image::INTERPOLATE_LANCZOS); + } + for (int32_t h = 0; h < height; h++) { + for (int32_t w = 0; w < width; w++) { + Color c = Color(1.0f, 1.0f, 1.0f); + if (has_ao) { + if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == ao_channel) { + c.r = ao_image->get_pixel(w, h).r; + } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == ao_channel) { + c.r = ao_image->get_pixel(w, h).g; + } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == ao_channel) { + c.r = ao_image->get_pixel(w, h).b; + } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == ao_channel) { + c.r = ao_image->get_pixel(w, h).a; + } } - } - if (has_roughness) { - if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == roughness_channel) { - c.g = roughness_image->get_pixel(w, h).r; - } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == roughness_channel) { - c.g = roughness_image->get_pixel(w, h).g; - } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == roughness_channel) { - c.g = roughness_image->get_pixel(w, h).b; - } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == roughness_channel) { - c.g = roughness_image->get_pixel(w, h).a; + if (has_roughness) { + if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == roughness_channel) { + c.g = roughness_image->get_pixel(w, h).r; + } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == roughness_channel) { + c.g = roughness_image->get_pixel(w, h).g; + } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == roughness_channel) { + c.g = roughness_image->get_pixel(w, h).b; + } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == roughness_channel) { + c.g = roughness_image->get_pixel(w, h).a; + } } - } - if (has_metalness) { - if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == metalness_channel) { - c.b = metallness_image->get_pixel(w, h).r; - } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == metalness_channel) { - c.b = metallness_image->get_pixel(w, h).g; - } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == metalness_channel) { - c.b = metallness_image->get_pixel(w, h).b; - } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == metalness_channel) { - c.b = metallness_image->get_pixel(w, h).a; + if (has_metalness) { + if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_RED == metalness_channel) { + c.b = metallness_image->get_pixel(w, h).r; + } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_GREEN == metalness_channel) { + c.b = metallness_image->get_pixel(w, h).g; + } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_BLUE == metalness_channel) { + c.b = metallness_image->get_pixel(w, h).b; + } else if (BaseMaterial3D::TextureChannel::TEXTURE_CHANNEL_ALPHA == metalness_channel) { + c.b = metallness_image->get_pixel(w, h).a; + } } + orm_image->set_pixel(w, h, c); } - orm_image->set_pixel(w, h, c); } - } - orm_image->generate_mipmaps(); - orm_texture->set_image(orm_image); - GLTFTextureIndex orm_texture_index = -1; - if (has_ao || has_roughness || has_metalness) { - orm_texture->set_name(material->get_name() + "_orm"); - orm_texture_index = _set_texture(state, orm_texture, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT)); - } - if (has_ao) { - Dictionary occt; - occt["index"] = orm_texture_index; - d["occlusionTexture"] = occt; - } - if (has_roughness || has_metalness) { - mrt["index"] = orm_texture_index; - Dictionary extensions = _serialize_texture_transform_uv1(material); - if (!extensions.is_empty()) { - mrt["extensions"] = extensions; - state->use_khr_texture_transform = true; + orm_image->generate_mipmaps(); + orm_texture->set_image(orm_image); + GLTFTextureIndex orm_texture_index = -1; + if (has_ao || has_roughness || has_metalness) { + orm_texture->set_name(material->get_name() + "_orm"); + orm_texture_index = _set_texture(p_state, orm_texture, base_material->get_texture_filter(), base_material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT)); + } + if (has_ao) { + Dictionary occt; + occt["index"] = orm_texture_index; + d["occlusionTexture"] = occt; + } + if (has_roughness || has_metalness) { + mrt["index"] = orm_texture_index; + Dictionary extensions = _serialize_texture_transform_uv1(material); + if (!extensions.is_empty()) { + mrt["extensions"] = extensions; + p_state->use_khr_texture_transform = true; + } + mr["metallicRoughnessTexture"] = mrt; } - mr["metallicRoughnessTexture"] = mrt; } } d["pbrMetallicRoughness"] = mr; } - - if (material->get_feature(BaseMaterial3D::FEATURE_NORMAL_MAPPING)) { + if (base_material->get_feature(BaseMaterial3D::FEATURE_NORMAL_MAPPING)) { Dictionary nt; Ref<ImageTexture> tex; tex.instantiate(); { - Ref<Texture2D> normal_texture = material->get_texture(BaseMaterial3D::TEXTURE_NORMAL); + Ref<Texture2D> normal_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_NORMAL); if (normal_texture.is_valid()) { // Code for uncompressing RG normal maps Ref<Image> img = normal_texture->get_image(); @@ -3593,30 +3590,30 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) { GLTFTextureIndex gltf_texture_index = -1; if (tex.is_valid() && tex->get_image().is_valid()) { tex->set_name(material->get_name() + "_normal"); - gltf_texture_index = _set_texture(state, tex, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT)); + gltf_texture_index = _set_texture(p_state, tex, base_material->get_texture_filter(), base_material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT)); } - nt["scale"] = material->get_normal_scale(); + nt["scale"] = base_material->get_normal_scale(); if (gltf_texture_index != -1) { nt["index"] = gltf_texture_index; d["normalTexture"] = nt; } } - if (material->get_feature(BaseMaterial3D::FEATURE_EMISSION)) { - const Color c = material->get_emission().linear_to_srgb(); + if (base_material->get_feature(BaseMaterial3D::FEATURE_EMISSION)) { + const Color c = base_material->get_emission().linear_to_srgb(); Array arr; arr.push_back(c.r); arr.push_back(c.g); arr.push_back(c.b); d["emissiveFactor"] = arr; } - if (material->get_feature(BaseMaterial3D::FEATURE_EMISSION)) { + if (base_material->get_feature(BaseMaterial3D::FEATURE_EMISSION)) { Dictionary et; - Ref<Texture2D> emission_texture = material->get_texture(BaseMaterial3D::TEXTURE_EMISSION); + Ref<Texture2D> emission_texture = base_material->get_texture(BaseMaterial3D::TEXTURE_EMISSION); GLTFTextureIndex gltf_texture_index = -1; if (emission_texture.is_valid() && emission_texture->get_image().is_valid()) { emission_texture->set_name(material->get_name() + "_emission"); - gltf_texture_index = _set_texture(state, emission_texture, material->get_texture_filter(), material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT)); + gltf_texture_index = _set_texture(p_state, emission_texture, base_material->get_texture_filter(), base_material->get_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT)); } if (gltf_texture_index != -1) { @@ -3624,14 +3621,14 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) { d["emissiveTexture"] = et; } } - const bool ds = material->get_cull_mode() == BaseMaterial3D::CULL_DISABLED; + const bool ds = base_material->get_cull_mode() == BaseMaterial3D::CULL_DISABLED; if (ds) { d["doubleSided"] = ds; } - if (material->get_transparency() == BaseMaterial3D::TRANSPARENCY_ALPHA_SCISSOR) { + if (base_material->get_transparency() == BaseMaterial3D::TRANSPARENCY_ALPHA_SCISSOR) { d["alphaMode"] = "MASK"; - d["alphaCutoff"] = material->get_alpha_scissor_threshold(); - } else if (material->get_transparency() != BaseMaterial3D::TRANSPARENCY_DISABLED) { + d["alphaCutoff"] = base_material->get_alpha_scissor_threshold(); + } else if (base_material->get_transparency() != BaseMaterial3D::TRANSPARENCY_DISABLED) { d["alphaMode"] = "BLEND"; } materials.push_back(d); @@ -3639,18 +3636,18 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) { if (!materials.size()) { return OK; } - state->json["materials"] = materials; - print_verbose("Total materials: " + itos(state->materials.size())); + p_state->json["materials"] = materials; + print_verbose("Total materials: " + itos(p_state->materials.size())); return OK; } -Error GLTFDocument::_parse_materials(Ref<GLTFState> state) { - if (!state->json.has("materials")) { +Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { + if (!p_state->json.has("materials")) { return OK; } - const Array &materials = state->json["materials"]; + const Array &materials = p_state->json["materials"]; for (GLTFMaterialIndex i = 0; i < materials.size(); i++) { const Dictionary &d = materials[i]; @@ -3675,12 +3672,12 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> state) { if (sgm.has("diffuseTexture")) { const Dictionary &diffuse_texture_dict = sgm["diffuseTexture"]; if (diffuse_texture_dict.has("index")) { - Ref<GLTFTextureSampler> diffuse_sampler = _get_sampler_for_texture(state, diffuse_texture_dict["index"]); + Ref<GLTFTextureSampler> diffuse_sampler = _get_sampler_for_texture(p_state, diffuse_texture_dict["index"]); if (diffuse_sampler.is_valid()) { material->set_texture_filter(diffuse_sampler->get_filter_mode()); material->set_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT, diffuse_sampler->get_wrap_mode()); } - Ref<Texture2D> diffuse_texture = _get_texture(state, diffuse_texture_dict["index"]); + Ref<Texture2D> diffuse_texture = _get_texture(p_state, diffuse_texture_dict["index"]); if (diffuse_texture.is_valid()) { spec_gloss->diffuse_img = diffuse_texture->get_image(); material->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, diffuse_texture); @@ -3708,7 +3705,7 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> state) { if (sgm.has("specularGlossinessTexture")) { const Dictionary &spec_gloss_texture = sgm["specularGlossinessTexture"]; if (spec_gloss_texture.has("index")) { - const Ref<Texture2D> orig_texture = _get_texture(state, spec_gloss_texture["index"]); + const Ref<Texture2D> orig_texture = _get_texture(p_state, spec_gloss_texture["index"]); if (orig_texture.is_valid()) { spec_gloss->spec_gloss_img = orig_texture->get_image(); } @@ -3728,10 +3725,10 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> state) { if (mr.has("baseColorTexture")) { const Dictionary &bct = mr["baseColorTexture"]; if (bct.has("index")) { - Ref<GLTFTextureSampler> bct_sampler = _get_sampler_for_texture(state, bct["index"]); + Ref<GLTFTextureSampler> bct_sampler = _get_sampler_for_texture(p_state, bct["index"]); material->set_texture_filter(bct_sampler->get_filter_mode()); material->set_flag(BaseMaterial3D::FLAG_USE_TEXTURE_REPEAT, bct_sampler->get_wrap_mode()); - material->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, _get_texture(state, bct["index"])); + material->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, _get_texture(p_state, bct["index"])); } if (!mr.has("baseColorFactor")) { material->set_albedo(Color(1, 1, 1)); @@ -3754,7 +3751,7 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> state) { if (mr.has("metallicRoughnessTexture")) { const Dictionary &bct = mr["metallicRoughnessTexture"]; if (bct.has("index")) { - const Ref<Texture2D> t = _get_texture(state, bct["index"]); + const Ref<Texture2D> t = _get_texture(p_state, bct["index"]); material->set_texture(BaseMaterial3D::TEXTURE_METALLIC, t); material->set_metallic_texture_channel(BaseMaterial3D::TEXTURE_CHANNEL_BLUE); material->set_texture(BaseMaterial3D::TEXTURE_ROUGHNESS, t); @@ -3772,7 +3769,7 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> state) { if (d.has("normalTexture")) { const Dictionary &bct = d["normalTexture"]; if (bct.has("index")) { - material->set_texture(BaseMaterial3D::TEXTURE_NORMAL, _get_texture(state, bct["index"])); + material->set_texture(BaseMaterial3D::TEXTURE_NORMAL, _get_texture(p_state, bct["index"])); material->set_feature(BaseMaterial3D::FEATURE_NORMAL_MAPPING, true); } if (bct.has("scale")) { @@ -3782,7 +3779,7 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> state) { if (d.has("occlusionTexture")) { const Dictionary &bct = d["occlusionTexture"]; if (bct.has("index")) { - material->set_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION, _get_texture(state, bct["index"])); + material->set_texture(BaseMaterial3D::TEXTURE_AMBIENT_OCCLUSION, _get_texture(p_state, bct["index"])); material->set_ao_texture_channel(BaseMaterial3D::TEXTURE_CHANNEL_RED); material->set_feature(BaseMaterial3D::FEATURE_AMBIENT_OCCLUSION, true); } @@ -3800,7 +3797,7 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> state) { if (d.has("emissiveTexture")) { const Dictionary &bct = d["emissiveTexture"]; if (bct.has("index")) { - material->set_texture(BaseMaterial3D::TEXTURE_EMISSION, _get_texture(state, bct["index"])); + material->set_texture(BaseMaterial3D::TEXTURE_EMISSION, _get_texture(p_state, bct["index"])); material->set_feature(BaseMaterial3D::FEATURE_EMISSION, true); material->set_emission(Color(0, 0, 0)); } @@ -3825,41 +3822,49 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> state) { } } } - state->materials.push_back(material); + p_state->materials.push_back(material); } - print_verbose("Total materials: " + itos(state->materials.size())); + print_verbose("Total materials: " + itos(p_state->materials.size())); return OK; } -void GLTFDocument::_set_texture_transform_uv1(const Dictionary &d, Ref<BaseMaterial3D> material) { - if (d.has("extensions")) { - const Dictionary &extensions = d["extensions"]; +void GLTFDocument::_set_texture_transform_uv1(const Dictionary &p_dict, Ref<BaseMaterial3D> p_material) { + if (p_dict.has("extensions")) { + const Dictionary &extensions = p_dict["extensions"]; if (extensions.has("KHR_texture_transform")) { - const Dictionary &texture_transform = extensions["KHR_texture_transform"]; - const Array &offset_arr = texture_transform["offset"]; - if (offset_arr.size() == 2) { - const Vector3 offset_vector3 = Vector3(offset_arr[0], offset_arr[1], 0.0f); - material->set_uv1_offset(offset_vector3); - } + if (p_material.is_valid()) { + const Dictionary &texture_transform = extensions["KHR_texture_transform"]; + const Array &offset_arr = texture_transform["offset"]; + if (offset_arr.size() == 2) { + const Vector3 offset_vector3 = Vector3(offset_arr[0], offset_arr[1], 0.0f); + p_material->set_uv1_offset(offset_vector3); + } - const Array &scale_arr = texture_transform["scale"]; - if (scale_arr.size() == 2) { - const Vector3 scale_vector3 = Vector3(scale_arr[0], scale_arr[1], 1.0f); - material->set_uv1_scale(scale_vector3); + const Array &scale_arr = texture_transform["scale"]; + if (scale_arr.size() == 2) { + const Vector3 scale_vector3 = Vector3(scale_arr[0], scale_arr[1], 1.0f); + p_material->set_uv1_scale(scale_vector3); + } } } } } void GLTFDocument::spec_gloss_to_rough_metal(Ref<GLTFSpecGloss> r_spec_gloss, Ref<BaseMaterial3D> p_material) { + if (r_spec_gloss.is_null()) { + return; + } if (r_spec_gloss->spec_gloss_img.is_null()) { return; } if (r_spec_gloss->diffuse_img.is_null()) { return; } + if (p_material.is_null()) { + return; + } bool has_roughness = false; bool has_metal = false; p_material->set_roughness(1.0f); @@ -3931,13 +3936,13 @@ void GLTFDocument::spec_gloss_to_metal_base_color(const Color &p_specular_factor r_base_color = r_base_color.clamp(); } -GLTFNodeIndex GLTFDocument::_find_highest_node(Ref<GLTFState> state, const Vector<GLTFNodeIndex> &subset) { +GLTFNodeIndex GLTFDocument::_find_highest_node(Ref<GLTFState> p_state, const Vector<GLTFNodeIndex> &p_subset) { int highest = -1; GLTFNodeIndex best_node = -1; - for (int i = 0; i < subset.size(); ++i) { - const GLTFNodeIndex node_i = subset[i]; - const Ref<GLTFNode> node = state->nodes[node_i]; + for (int i = 0; i < p_subset.size(); ++i) { + const GLTFNodeIndex node_i = p_subset[i]; + const Ref<GLTFNode> node = p_state->nodes[node_i]; if (highest == -1 || node->height < highest) { highest = node->height; @@ -3948,38 +3953,38 @@ GLTFNodeIndex GLTFDocument::_find_highest_node(Ref<GLTFState> state, const Vecto return best_node; } -bool GLTFDocument::_capture_nodes_in_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin, const GLTFNodeIndex node_index) { +bool GLTFDocument::_capture_nodes_in_skin(Ref<GLTFState> p_state, Ref<GLTFSkin> p_skin, const GLTFNodeIndex p_node_index) { bool found_joint = false; - for (int i = 0; i < state->nodes[node_index]->children.size(); ++i) { - found_joint |= _capture_nodes_in_skin(state, skin, state->nodes[node_index]->children[i]); + for (int i = 0; i < p_state->nodes[p_node_index]->children.size(); ++i) { + found_joint |= _capture_nodes_in_skin(p_state, p_skin, p_state->nodes[p_node_index]->children[i]); } if (found_joint) { // Mark it if we happen to find another skins joint... - if (state->nodes[node_index]->joint && skin->joints.find(node_index) < 0) { - skin->joints.push_back(node_index); - } else if (skin->non_joints.find(node_index) < 0) { - skin->non_joints.push_back(node_index); + if (p_state->nodes[p_node_index]->joint && p_skin->joints.find(p_node_index) < 0) { + p_skin->joints.push_back(p_node_index); + } else if (p_skin->non_joints.find(p_node_index) < 0) { + p_skin->non_joints.push_back(p_node_index); } } - if (skin->joints.find(node_index) > 0) { + if (p_skin->joints.find(p_node_index) > 0) { return true; } return false; } -void GLTFDocument::_capture_nodes_for_multirooted_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin) { +void GLTFDocument::_capture_nodes_for_multirooted_skin(Ref<GLTFState> p_state, Ref<GLTFSkin> p_skin) { DisjointSet<GLTFNodeIndex> disjoint_set; - for (int i = 0; i < skin->joints.size(); ++i) { - const GLTFNodeIndex node_index = skin->joints[i]; - const GLTFNodeIndex parent = state->nodes[node_index]->parent; + for (int i = 0; i < p_skin->joints.size(); ++i) { + const GLTFNodeIndex node_index = p_skin->joints[i]; + const GLTFNodeIndex parent = p_state->nodes[node_index]->parent; disjoint_set.insert(node_index); - if (skin->joints.find(parent) >= 0) { + if (p_skin->joints.find(parent) >= 0) { disjoint_set.create_union(parent, node_index); } } @@ -3997,8 +4002,8 @@ void GLTFDocument::_capture_nodes_for_multirooted_skin(Ref<GLTFState> state, Ref for (int i = 0; i < roots.size(); ++i) { const GLTFNodeIndex root = roots[i]; - if (maxHeight == -1 || state->nodes[root]->height < maxHeight) { - maxHeight = state->nodes[root]->height; + if (maxHeight == -1 || p_state->nodes[root]->height < maxHeight) { + maxHeight = p_state->nodes[root]->height; } } @@ -4006,13 +4011,13 @@ void GLTFDocument::_capture_nodes_for_multirooted_skin(Ref<GLTFState> state, Ref // This sucks, but 99% of all game engines (not just Godot) would have this same issue. for (int i = 0; i < roots.size(); ++i) { GLTFNodeIndex current_node = roots[i]; - while (state->nodes[current_node]->height > maxHeight) { - GLTFNodeIndex parent = state->nodes[current_node]->parent; + while (p_state->nodes[current_node]->height > maxHeight) { + GLTFNodeIndex parent = p_state->nodes[current_node]->parent; - if (state->nodes[parent]->joint && skin->joints.find(parent) < 0) { - skin->joints.push_back(parent); - } else if (skin->non_joints.find(parent) < 0) { - skin->non_joints.push_back(parent); + if (p_state->nodes[parent]->joint && p_skin->joints.find(parent) < 0) { + p_skin->joints.push_back(parent); + } else if (p_skin->non_joints.find(parent) < 0) { + p_skin->non_joints.push_back(parent); } current_node = parent; @@ -4027,21 +4032,21 @@ void GLTFDocument::_capture_nodes_for_multirooted_skin(Ref<GLTFState> state, Ref do { all_same = true; - const GLTFNodeIndex first_parent = state->nodes[roots[0]]->parent; + const GLTFNodeIndex first_parent = p_state->nodes[roots[0]]->parent; for (int i = 1; i < roots.size(); ++i) { - all_same &= (first_parent == state->nodes[roots[i]]->parent); + all_same &= (first_parent == p_state->nodes[roots[i]]->parent); } if (!all_same) { for (int i = 0; i < roots.size(); ++i) { const GLTFNodeIndex current_node = roots[i]; - const GLTFNodeIndex parent = state->nodes[current_node]->parent; + const GLTFNodeIndex parent = p_state->nodes[current_node]->parent; - if (state->nodes[parent]->joint && skin->joints.find(parent) < 0) { - skin->joints.push_back(parent); - } else if (skin->non_joints.find(parent) < 0) { - skin->non_joints.push_back(parent); + if (p_state->nodes[parent]->joint && p_skin->joints.find(parent) < 0) { + p_skin->joints.push_back(parent); + } else if (p_skin->non_joints.find(parent) < 0) { + p_skin->non_joints.push_back(parent); } roots.write[i] = parent; @@ -4051,19 +4056,19 @@ void GLTFDocument::_capture_nodes_for_multirooted_skin(Ref<GLTFState> state, Ref } while (!all_same); } -Error GLTFDocument::_expand_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin) { - _capture_nodes_for_multirooted_skin(state, skin); +Error GLTFDocument::_expand_skin(Ref<GLTFState> p_state, Ref<GLTFSkin> p_skin) { + _capture_nodes_for_multirooted_skin(p_state, p_skin); // Grab all nodes that lay in between skin joints/nodes DisjointSet<GLTFNodeIndex> disjoint_set; Vector<GLTFNodeIndex> all_skin_nodes; - all_skin_nodes.append_array(skin->joints); - all_skin_nodes.append_array(skin->non_joints); + all_skin_nodes.append_array(p_skin->joints); + all_skin_nodes.append_array(p_skin->non_joints); for (int i = 0; i < all_skin_nodes.size(); ++i) { const GLTFNodeIndex node_index = all_skin_nodes[i]; - const GLTFNodeIndex parent = state->nodes[node_index]->parent; + const GLTFNodeIndex parent = p_state->nodes[node_index]->parent; disjoint_set.insert(node_index); if (all_skin_nodes.find(parent) >= 0) { @@ -4080,7 +4085,7 @@ Error GLTFDocument::_expand_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin) { Vector<GLTFNodeIndex> set; disjoint_set.get_members(set, out_owners[i]); - const GLTFNodeIndex root = _find_highest_node(state, set); + const GLTFNodeIndex root = _find_highest_node(p_state, set); ERR_FAIL_COND_V(root < 0, FAILED); out_roots.push_back(root); } @@ -4088,15 +4093,15 @@ Error GLTFDocument::_expand_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin) { out_roots.sort(); for (int i = 0; i < out_roots.size(); ++i) { - _capture_nodes_in_skin(state, skin, out_roots[i]); + _capture_nodes_in_skin(p_state, p_skin, out_roots[i]); } - skin->roots = out_roots; + p_skin->roots = out_roots; return OK; } -Error GLTFDocument::_verify_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin) { +Error GLTFDocument::_verify_skin(Ref<GLTFState> p_state, Ref<GLTFSkin> p_skin) { // This may seem duplicated from expand_skins, but this is really a sanity check! (so it kinda is) // In case additional interpolating logic is added to the skins, this will help ensure that you // do not cause it to self implode into a fiery blaze @@ -4108,12 +4113,12 @@ Error GLTFDocument::_verify_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin) { DisjointSet<GLTFNodeIndex> disjoint_set; Vector<GLTFNodeIndex> all_skin_nodes; - all_skin_nodes.append_array(skin->joints); - all_skin_nodes.append_array(skin->non_joints); + all_skin_nodes.append_array(p_skin->joints); + all_skin_nodes.append_array(p_skin->non_joints); for (int i = 0; i < all_skin_nodes.size(); ++i) { const GLTFNodeIndex node_index = all_skin_nodes[i]; - const GLTFNodeIndex parent = state->nodes[node_index]->parent; + const GLTFNodeIndex parent = p_state->nodes[node_index]->parent; disjoint_set.insert(node_index); if (all_skin_nodes.find(parent) >= 0) { @@ -4130,7 +4135,7 @@ Error GLTFDocument::_verify_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin) { Vector<GLTFNodeIndex> set; disjoint_set.get_members(set, out_owners[i]); - const GLTFNodeIndex root = _find_highest_node(state, set); + const GLTFNodeIndex root = _find_highest_node(p_state, set); ERR_FAIL_COND_V(root < 0, FAILED); out_roots.push_back(root); } @@ -4140,9 +4145,9 @@ Error GLTFDocument::_verify_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin) { ERR_FAIL_COND_V(out_roots.size() == 0, FAILED); // Make sure the roots are the exact same (they better be) - ERR_FAIL_COND_V(out_roots.size() != skin->roots.size(), FAILED); + ERR_FAIL_COND_V(out_roots.size() != p_skin->roots.size(), FAILED); for (int i = 0; i < out_roots.size(); ++i) { - ERR_FAIL_COND_V(out_roots[i] != skin->roots[i], FAILED); + ERR_FAIL_COND_V(out_roots[i] != p_skin->roots[i], FAILED); } // Single rooted skin? Perfectly ok! @@ -4151,9 +4156,9 @@ Error GLTFDocument::_verify_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin) { } // Make sure all parents of a multi-rooted skin are the SAME - const GLTFNodeIndex parent = state->nodes[out_roots[0]]->parent; + const GLTFNodeIndex parent = p_state->nodes[out_roots[0]]->parent; for (int i = 1; i < out_roots.size(); ++i) { - if (state->nodes[out_roots[i]]->parent != parent) { + if (p_state->nodes[out_roots[i]]->parent != parent) { return FAILED; } } @@ -4161,12 +4166,12 @@ Error GLTFDocument::_verify_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin) { return OK; } -Error GLTFDocument::_parse_skins(Ref<GLTFState> state) { - if (!state->json.has("skins")) { +Error GLTFDocument::_parse_skins(Ref<GLTFState> p_state) { + if (!p_state->json.has("skins")) { return OK; } - const Array &skins = state->json["skins"]; + const Array &skins = p_state->json["skins"]; // Create the base skins, and mark nodes that are joints for (int i = 0; i < skins.size(); i++) { @@ -4180,18 +4185,18 @@ Error GLTFDocument::_parse_skins(Ref<GLTFState> state) { const Array &joints = d["joints"]; if (d.has("inverseBindMatrices")) { - skin->inverse_binds = _decode_accessor_as_xform(state, d["inverseBindMatrices"], false); + skin->inverse_binds = _decode_accessor_as_xform(p_state, d["inverseBindMatrices"], false); ERR_FAIL_COND_V(skin->inverse_binds.size() != joints.size(), ERR_PARSE_ERROR); } for (int j = 0; j < joints.size(); j++) { const GLTFNodeIndex node = joints[j]; - ERR_FAIL_INDEX_V(node, state->nodes.size(), ERR_PARSE_ERROR); + ERR_FAIL_INDEX_V(node, p_state->nodes.size(), ERR_PARSE_ERROR); skin->joints.push_back(node); skin->joints_original.push_back(node); - state->nodes.write[node]->joint = true; + p_state->nodes.write[node]->joint = true; } if (d.has("name") && !String(d["name"]).is_empty()) { @@ -4204,32 +4209,32 @@ Error GLTFDocument::_parse_skins(Ref<GLTFState> state) { skin->skin_root = d["skeleton"]; } - state->skins.push_back(skin); + p_state->skins.push_back(skin); } - for (GLTFSkinIndex i = 0; i < state->skins.size(); ++i) { - Ref<GLTFSkin> skin = state->skins.write[i]; + for (GLTFSkinIndex i = 0; i < p_state->skins.size(); ++i) { + Ref<GLTFSkin> skin = p_state->skins.write[i]; // Expand the skin to capture all the extra non-joints that lie in between the actual joints, // and expand the hierarchy to ensure multi-rooted trees lie on the same height level - ERR_FAIL_COND_V(_expand_skin(state, skin), ERR_PARSE_ERROR); - ERR_FAIL_COND_V(_verify_skin(state, skin), ERR_PARSE_ERROR); + ERR_FAIL_COND_V(_expand_skin(p_state, skin), ERR_PARSE_ERROR); + ERR_FAIL_COND_V(_verify_skin(p_state, skin), ERR_PARSE_ERROR); } - print_verbose("glTF: Total skins: " + itos(state->skins.size())); + print_verbose("glTF: Total skins: " + itos(p_state->skins.size())); return OK; } -Error GLTFDocument::_determine_skeletons(Ref<GLTFState> state) { +Error GLTFDocument::_determine_skeletons(Ref<GLTFState> p_state) { // Using a disjoint set, we are going to potentially combine all skins that are actually branches // of a main skeleton, or treat skins defining the same set of nodes as ONE skeleton. // This is another unclear issue caused by the current glTF specification. DisjointSet<GLTFNodeIndex> skeleton_sets; - for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) { - const Ref<GLTFSkin> skin = state->skins[skin_i]; + for (GLTFSkinIndex skin_i = 0; skin_i < p_state->skins.size(); ++skin_i) { + const Ref<GLTFSkin> skin = p_state->skins[skin_i]; Vector<GLTFNodeIndex> all_skin_nodes; all_skin_nodes.append_array(skin->joints); @@ -4237,7 +4242,7 @@ Error GLTFDocument::_determine_skeletons(Ref<GLTFState> state) { for (int i = 0; i < all_skin_nodes.size(); ++i) { const GLTFNodeIndex node_index = all_skin_nodes[i]; - const GLTFNodeIndex parent = state->nodes[node_index]->parent; + const GLTFNodeIndex parent = p_state->nodes[node_index]->parent; skeleton_sets.insert(node_index); if (all_skin_nodes.find(parent) >= 0) { @@ -4261,7 +4266,7 @@ Error GLTFDocument::_determine_skeletons(Ref<GLTFState> state) { for (int i = 0; i < groups_representatives.size(); ++i) { Vector<GLTFNodeIndex> group; skeleton_sets.get_members(group, groups_representatives[i]); - highest_group_members.push_back(_find_highest_node(state, group)); + highest_group_members.push_back(_find_highest_node(p_state, group)); groups.push_back(group); } @@ -4273,13 +4278,13 @@ Error GLTFDocument::_determine_skeletons(Ref<GLTFState> state) { const GLTFNodeIndex node_j = highest_group_members[j]; // Even if they are siblings under the root! :) - if (state->nodes[node_i]->parent == state->nodes[node_j]->parent) { + if (p_state->nodes[node_i]->parent == p_state->nodes[node_j]->parent) { skeleton_sets.create_union(node_i, node_j); } } // Attach any parenting going on together (we need to do this n^2 times) - const GLTFNodeIndex node_i_parent = state->nodes[node_i]->parent; + const GLTFNodeIndex node_i_parent = p_state->nodes[node_i]->parent; if (node_i_parent >= 0) { for (int j = 0; j < groups.size() && i != j; ++j) { const Vector<GLTFNodeIndex> &group = groups[j]; @@ -4306,8 +4311,8 @@ Error GLTFDocument::_determine_skeletons(Ref<GLTFState> state) { Vector<GLTFNodeIndex> skeleton_nodes; skeleton_sets.get_members(skeleton_nodes, skeleton_owner); - for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) { - Ref<GLTFSkin> skin = state->skins.write[skin_i]; + for (GLTFSkinIndex skin_i = 0; skin_i < p_state->skins.size(); ++skin_i) { + Ref<GLTFSkin> skin = p_state->skins.write[skin_i]; // If any of the the skeletons nodes exist in a skin, that skin now maps to the skeleton for (int i = 0; i < skeleton_nodes.size(); ++i) { @@ -4323,37 +4328,37 @@ Error GLTFDocument::_determine_skeletons(Ref<GLTFState> state) { for (int i = 0; i < skeleton_nodes.size(); ++i) { const GLTFNodeIndex node_i = skeleton_nodes[i]; - if (state->nodes[node_i]->joint) { + if (p_state->nodes[node_i]->joint) { skeleton->joints.push_back(node_i); } else { non_joints.push_back(node_i); } } - state->skeletons.push_back(skeleton); + p_state->skeletons.push_back(skeleton); - _reparent_non_joint_skeleton_subtrees(state, state->skeletons.write[skel_i], non_joints); + _reparent_non_joint_skeleton_subtrees(p_state, p_state->skeletons.write[skel_i], non_joints); } - for (GLTFSkeletonIndex skel_i = 0; skel_i < state->skeletons.size(); ++skel_i) { - Ref<GLTFSkeleton> skeleton = state->skeletons.write[skel_i]; + for (GLTFSkeletonIndex skel_i = 0; skel_i < p_state->skeletons.size(); ++skel_i) { + Ref<GLTFSkeleton> skeleton = p_state->skeletons.write[skel_i]; for (int i = 0; i < skeleton->joints.size(); ++i) { const GLTFNodeIndex node_i = skeleton->joints[i]; - Ref<GLTFNode> node = state->nodes[node_i]; + Ref<GLTFNode> node = p_state->nodes[node_i]; ERR_FAIL_COND_V(!node->joint, ERR_PARSE_ERROR); ERR_FAIL_COND_V(node->skeleton >= 0, ERR_PARSE_ERROR); node->skeleton = skel_i; } - ERR_FAIL_COND_V(_determine_skeleton_roots(state, skel_i), ERR_PARSE_ERROR); + ERR_FAIL_COND_V(_determine_skeleton_roots(p_state, skel_i), ERR_PARSE_ERROR); } return OK; } -Error GLTFDocument::_reparent_non_joint_skeleton_subtrees(Ref<GLTFState> state, Ref<GLTFSkeleton> skeleton, const Vector<GLTFNodeIndex> &non_joints) { +Error GLTFDocument::_reparent_non_joint_skeleton_subtrees(Ref<GLTFState> p_state, Ref<GLTFSkeleton> p_skeleton, const Vector<GLTFNodeIndex> &p_non_joints) { DisjointSet<GLTFNodeIndex> subtree_set; // Populate the disjoint set with ONLY non joints that are in the skeleton hierarchy (non_joints vector) @@ -4364,13 +4369,13 @@ Error GLTFDocument::_reparent_non_joint_skeleton_subtrees(Ref<GLTFState> state, // skinD depicted here explains this issue: // https://github.com/KhronosGroup/glTF-Asset-Generator/blob/master/Output/Positive/Animation_Skin - for (int i = 0; i < non_joints.size(); ++i) { - const GLTFNodeIndex node_i = non_joints[i]; + for (int i = 0; i < p_non_joints.size(); ++i) { + const GLTFNodeIndex node_i = p_non_joints[i]; subtree_set.insert(node_i); - const GLTFNodeIndex parent_i = state->nodes[node_i]->parent; - if (parent_i >= 0 && non_joints.find(parent_i) >= 0 && !state->nodes[parent_i]->joint) { + const GLTFNodeIndex parent_i = p_state->nodes[node_i]->parent; + if (parent_i >= 0 && p_non_joints.find(parent_i) >= 0 && !p_state->nodes[parent_i]->joint) { subtree_set.create_union(parent_i, node_i); } } @@ -4387,34 +4392,34 @@ Error GLTFDocument::_reparent_non_joint_skeleton_subtrees(Ref<GLTFState> state, subtree_set.get_members(subtree_nodes, subtree_root); for (int subtree_i = 0; subtree_i < subtree_nodes.size(); ++subtree_i) { - Ref<GLTFNode> node = state->nodes[subtree_nodes[subtree_i]]; + Ref<GLTFNode> node = p_state->nodes[subtree_nodes[subtree_i]]; node->joint = true; // Add the joint to the skeletons joints - skeleton->joints.push_back(subtree_nodes[subtree_i]); + p_skeleton->joints.push_back(subtree_nodes[subtree_i]); } } return OK; } -Error GLTFDocument::_determine_skeleton_roots(Ref<GLTFState> state, const GLTFSkeletonIndex skel_i) { +Error GLTFDocument::_determine_skeleton_roots(Ref<GLTFState> p_state, const GLTFSkeletonIndex p_skel_i) { DisjointSet<GLTFNodeIndex> disjoint_set; - for (GLTFNodeIndex i = 0; i < state->nodes.size(); ++i) { - const Ref<GLTFNode> node = state->nodes[i]; + for (GLTFNodeIndex i = 0; i < p_state->nodes.size(); ++i) { + const Ref<GLTFNode> node = p_state->nodes[i]; - if (node->skeleton != skel_i) { + if (node->skeleton != p_skel_i) { continue; } disjoint_set.insert(i); - if (node->parent >= 0 && state->nodes[node->parent]->skeleton == skel_i) { + if (node->parent >= 0 && p_state->nodes[node->parent]->skeleton == p_skel_i) { disjoint_set.create_union(node->parent, i); } } - Ref<GLTFSkeleton> skeleton = state->skeletons.write[skel_i]; + Ref<GLTFSkeleton> skeleton = p_state->skeletons.write[p_skel_i]; Vector<GLTFNodeIndex> representatives; disjoint_set.get_representatives(representatives); @@ -4424,7 +4429,7 @@ Error GLTFDocument::_determine_skeleton_roots(Ref<GLTFState> state, const GLTFSk for (int i = 0; i < representatives.size(); ++i) { Vector<GLTFNodeIndex> set; disjoint_set.get_members(set, representatives[i]); - const GLTFNodeIndex root = _find_highest_node(state, set); + const GLTFNodeIndex root = _find_highest_node(p_state, set); ERR_FAIL_COND_V(root < 0, FAILED); roots.push_back(root); } @@ -4440,9 +4445,9 @@ Error GLTFDocument::_determine_skeleton_roots(Ref<GLTFState> state, const GLTFSk } // Check that the subtrees have the same parent root - const GLTFNodeIndex parent = state->nodes[roots[0]]->parent; + const GLTFNodeIndex parent = p_state->nodes[roots[0]]->parent; for (int i = 1; i < roots.size(); ++i) { - if (state->nodes[roots[i]]->parent != parent) { + if (p_state->nodes[roots[i]]->parent != parent) { return FAILED; } } @@ -4450,16 +4455,16 @@ Error GLTFDocument::_determine_skeleton_roots(Ref<GLTFState> state, const GLTFSk return OK; } -Error GLTFDocument::_create_skeletons(Ref<GLTFState> state) { - for (GLTFSkeletonIndex skel_i = 0; skel_i < state->skeletons.size(); ++skel_i) { - Ref<GLTFSkeleton> gltf_skeleton = state->skeletons.write[skel_i]; +Error GLTFDocument::_create_skeletons(Ref<GLTFState> p_state) { + for (GLTFSkeletonIndex skel_i = 0; skel_i < p_state->skeletons.size(); ++skel_i) { + Ref<GLTFSkeleton> gltf_skeleton = p_state->skeletons.write[skel_i]; Skeleton3D *skeleton = memnew(Skeleton3D); gltf_skeleton->godot_skeleton = skeleton; - state->skeleton3d_to_gltf_skeleton[skeleton->get_instance_id()] = skel_i; + p_state->skeleton3d_to_gltf_skeleton[skeleton->get_instance_id()] = skel_i; // Make a unique name, no gltf node represents this skeleton - skeleton->set_name(_gen_unique_name(state, "Skeleton3D")); + skeleton->set_name(_gen_unique_name(p_state, "Skeleton3D")); List<GLTFNodeIndex> bones; @@ -4475,14 +4480,14 @@ Error GLTFDocument::_create_skeletons(Ref<GLTFState> state) { const GLTFNodeIndex node_i = bones.front()->get(); bones.pop_front(); - Ref<GLTFNode> node = state->nodes[node_i]; + Ref<GLTFNode> node = p_state->nodes[node_i]; ERR_FAIL_COND_V(node->skeleton != skel_i, FAILED); { // Add all child nodes to the stack (deterministically) Vector<GLTFNodeIndex> child_nodes; for (int i = 0; i < node->children.size(); ++i) { const GLTFNodeIndex child_i = node->children[i]; - if (state->nodes[child_i]->skeleton == skel_i) { + if (p_state->nodes[child_i]->skeleton == skel_i) { child_nodes.push_back(child_i); } } @@ -4500,7 +4505,7 @@ Error GLTFDocument::_create_skeletons(Ref<GLTFState> state) { node->set_name("bone"); } - node->set_name(_gen_unique_bone_name(state, skel_i, node->get_name())); + node->set_name(_gen_unique_bone_name(p_state, skel_i, node->get_name())); skeleton->add_bone(node->get_name()); skeleton->set_bone_rest(bone_index, node->xform); @@ -4508,30 +4513,30 @@ Error GLTFDocument::_create_skeletons(Ref<GLTFState> state) { skeleton->set_bone_pose_rotation(bone_index, node->rotation.normalized()); skeleton->set_bone_pose_scale(bone_index, node->scale); - if (node->parent >= 0 && state->nodes[node->parent]->skeleton == skel_i) { - const int bone_parent = skeleton->find_bone(state->nodes[node->parent]->get_name()); + if (node->parent >= 0 && p_state->nodes[node->parent]->skeleton == skel_i) { + const int bone_parent = skeleton->find_bone(p_state->nodes[node->parent]->get_name()); ERR_FAIL_COND_V(bone_parent < 0, FAILED); - skeleton->set_bone_parent(bone_index, skeleton->find_bone(state->nodes[node->parent]->get_name())); + skeleton->set_bone_parent(bone_index, skeleton->find_bone(p_state->nodes[node->parent]->get_name())); } - state->scene_nodes.insert(node_i, skeleton); + p_state->scene_nodes.insert(node_i, skeleton); } } - ERR_FAIL_COND_V(_map_skin_joints_indices_to_skeleton_bone_indices(state), ERR_PARSE_ERROR); + ERR_FAIL_COND_V(_map_skin_joints_indices_to_skeleton_bone_indices(p_state), ERR_PARSE_ERROR); return OK; } -Error GLTFDocument::_map_skin_joints_indices_to_skeleton_bone_indices(Ref<GLTFState> state) { - for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) { - Ref<GLTFSkin> skin = state->skins.write[skin_i]; +Error GLTFDocument::_map_skin_joints_indices_to_skeleton_bone_indices(Ref<GLTFState> p_state) { + for (GLTFSkinIndex skin_i = 0; skin_i < p_state->skins.size(); ++skin_i) { + Ref<GLTFSkin> skin = p_state->skins.write[skin_i]; - Ref<GLTFSkeleton> skeleton = state->skeletons[skin->skeleton]; + Ref<GLTFSkeleton> skeleton = p_state->skeletons[skin->skeleton]; for (int joint_index = 0; joint_index < skin->joints_original.size(); ++joint_index) { const GLTFNodeIndex node_i = skin->joints_original[joint_index]; - const Ref<GLTFNode> node = state->nodes[node_i]; + const Ref<GLTFNode> node = p_state->nodes[node_i]; const int bone_index = skeleton->godot_skeleton->find_bone(node->get_name()); ERR_FAIL_COND_V(bone_index < 0, FAILED); @@ -4543,28 +4548,28 @@ Error GLTFDocument::_map_skin_joints_indices_to_skeleton_bone_indices(Ref<GLTFSt return OK; } -Error GLTFDocument::_serialize_skins(Ref<GLTFState> state) { - _remove_duplicate_skins(state); +Error GLTFDocument::_serialize_skins(Ref<GLTFState> p_state) { + _remove_duplicate_skins(p_state); Array json_skins; - for (int skin_i = 0; skin_i < state->skins.size(); skin_i++) { - Ref<GLTFSkin> gltf_skin = state->skins[skin_i]; + for (int skin_i = 0; skin_i < p_state->skins.size(); skin_i++) { + Ref<GLTFSkin> gltf_skin = p_state->skins[skin_i]; Dictionary json_skin; - json_skin["inverseBindMatrices"] = _encode_accessor_as_xform(state, gltf_skin->inverse_binds, false); + json_skin["inverseBindMatrices"] = _encode_accessor_as_xform(p_state, gltf_skin->inverse_binds, false); json_skin["joints"] = gltf_skin->get_joints(); json_skin["name"] = gltf_skin->get_name(); json_skins.push_back(json_skin); } - if (!state->skins.size()) { + if (!p_state->skins.size()) { return OK; } - state->json["skins"] = json_skins; + p_state->json["skins"] = json_skins; return OK; } -Error GLTFDocument::_create_skins(Ref<GLTFState> state) { - for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) { - Ref<GLTFSkin> gltf_skin = state->skins.write[skin_i]; +Error GLTFDocument::_create_skins(Ref<GLTFState> p_state) { + for (GLTFSkinIndex skin_i = 0; skin_i < p_state->skins.size(); ++skin_i) { + Ref<GLTFSkin> gltf_skin = p_state->skins.write[skin_i]; Ref<Skin> skin; skin.instantiate(); @@ -4574,14 +4579,14 @@ Error GLTFDocument::_create_skins(Ref<GLTFState> state) { for (int joint_i = 0; joint_i < gltf_skin->joints_original.size(); ++joint_i) { GLTFNodeIndex node = gltf_skin->joints_original[joint_i]; - String bone_name = state->nodes[node]->get_name(); + String bone_name = p_state->nodes[node]->get_name(); Transform3D xform; if (has_ibms) { xform = gltf_skin->inverse_binds[joint_i]; } - if (state->use_named_skin_binds) { + if (p_state->use_named_skin_binds) { skin->add_named_bind(bone_name, xform); } else { int32_t bone_i = gltf_skin->joint_i_to_bone_i[joint_i]; @@ -4593,35 +4598,35 @@ Error GLTFDocument::_create_skins(Ref<GLTFState> state) { } // Purge the duplicates! - _remove_duplicate_skins(state); + _remove_duplicate_skins(p_state); // Create unique names now, after removing duplicates - for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) { - Ref<Skin> skin = state->skins.write[skin_i]->godot_skin; + for (GLTFSkinIndex skin_i = 0; skin_i < p_state->skins.size(); ++skin_i) { + Ref<Skin> skin = p_state->skins.write[skin_i]->godot_skin; if (skin->get_name().is_empty()) { // Make a unique name, no gltf node represents this skin - skin->set_name(_gen_unique_name(state, "Skin")); + skin->set_name(_gen_unique_name(p_state, "Skin")); } } return OK; } -bool GLTFDocument::_skins_are_same(const Ref<Skin> skin_a, const Ref<Skin> skin_b) { - if (skin_a->get_bind_count() != skin_b->get_bind_count()) { +bool GLTFDocument::_skins_are_same(const Ref<Skin> p_skin_a, const Ref<Skin> p_skin_b) { + if (p_skin_a->get_bind_count() != p_skin_b->get_bind_count()) { return false; } - for (int i = 0; i < skin_a->get_bind_count(); ++i) { - if (skin_a->get_bind_bone(i) != skin_b->get_bind_bone(i)) { + for (int i = 0; i < p_skin_a->get_bind_count(); ++i) { + if (p_skin_a->get_bind_bone(i) != p_skin_b->get_bind_bone(i)) { return false; } - if (skin_a->get_bind_name(i) != skin_b->get_bind_name(i)) { + if (p_skin_a->get_bind_name(i) != p_skin_b->get_bind_name(i)) { return false; } - Transform3D a_xform = skin_a->get_bind_pose(i); - Transform3D b_xform = skin_b->get_bind_pose(i); + Transform3D a_xform = p_skin_a->get_bind_pose(i); + Transform3D b_xform = p_skin_b->get_bind_pose(i); if (a_xform != b_xform) { return false; @@ -4631,67 +4636,67 @@ bool GLTFDocument::_skins_are_same(const Ref<Skin> skin_a, const Ref<Skin> skin_ return true; } -void GLTFDocument::_remove_duplicate_skins(Ref<GLTFState> state) { - for (int i = 0; i < state->skins.size(); ++i) { - for (int j = i + 1; j < state->skins.size(); ++j) { - const Ref<Skin> skin_i = state->skins[i]->godot_skin; - const Ref<Skin> skin_j = state->skins[j]->godot_skin; +void GLTFDocument::_remove_duplicate_skins(Ref<GLTFState> p_state) { + for (int i = 0; i < p_state->skins.size(); ++i) { + for (int j = i + 1; j < p_state->skins.size(); ++j) { + const Ref<Skin> skin_i = p_state->skins[i]->godot_skin; + const Ref<Skin> skin_j = p_state->skins[j]->godot_skin; if (_skins_are_same(skin_i, skin_j)) { // replace it and delete the old - state->skins.write[j]->godot_skin = skin_i; + p_state->skins.write[j]->godot_skin = skin_i; } } } } -Error GLTFDocument::_serialize_lights(Ref<GLTFState> state) { - if (state->lights.is_empty()) { +Error GLTFDocument::_serialize_lights(Ref<GLTFState> p_state) { + if (p_state->lights.is_empty()) { return OK; } Array lights; - for (GLTFLightIndex i = 0; i < state->lights.size(); i++) { - lights.push_back(state->lights[i]->to_dictionary()); + for (GLTFLightIndex i = 0; i < p_state->lights.size(); i++) { + lights.push_back(p_state->lights[i]->to_dictionary()); } Dictionary extensions; - if (state->json.has("extensions")) { - extensions = state->json["extensions"]; + if (p_state->json.has("extensions")) { + extensions = p_state->json["extensions"]; } else { - state->json["extensions"] = extensions; + p_state->json["extensions"] = extensions; } Dictionary lights_punctual; extensions["KHR_lights_punctual"] = lights_punctual; lights_punctual["lights"] = lights; - print_verbose("glTF: Total lights: " + itos(state->lights.size())); + print_verbose("glTF: Total lights: " + itos(p_state->lights.size())); return OK; } -Error GLTFDocument::_serialize_cameras(Ref<GLTFState> state) { +Error GLTFDocument::_serialize_cameras(Ref<GLTFState> p_state) { Array cameras; - cameras.resize(state->cameras.size()); - for (GLTFCameraIndex i = 0; i < state->cameras.size(); i++) { - cameras[i] = state->cameras[i]->to_dictionary(); + cameras.resize(p_state->cameras.size()); + for (GLTFCameraIndex i = 0; i < p_state->cameras.size(); i++) { + cameras[i] = p_state->cameras[i]->to_dictionary(); } - if (!state->cameras.size()) { + if (!p_state->cameras.size()) { return OK; } - state->json["cameras"] = cameras; + p_state->json["cameras"] = cameras; - print_verbose("glTF: Total cameras: " + itos(state->cameras.size())); + print_verbose("glTF: Total cameras: " + itos(p_state->cameras.size())); return OK; } -Error GLTFDocument::_parse_lights(Ref<GLTFState> state) { - if (!state->json.has("extensions")) { +Error GLTFDocument::_parse_lights(Ref<GLTFState> p_state) { + if (!p_state->json.has("extensions")) { return OK; } - Dictionary extensions = state->json["extensions"]; + Dictionary extensions = p_state->json["extensions"]; if (!extensions.has("KHR_lights_punctual")) { return OK; } @@ -4707,26 +4712,26 @@ Error GLTFDocument::_parse_lights(Ref<GLTFState> state) { if (light.is_null()) { return Error::ERR_PARSE_ERROR; } - state->lights.push_back(light); + p_state->lights.push_back(light); } - print_verbose("glTF: Total lights: " + itos(state->lights.size())); + print_verbose("glTF: Total lights: " + itos(p_state->lights.size())); return OK; } -Error GLTFDocument::_parse_cameras(Ref<GLTFState> state) { - if (!state->json.has("cameras")) { +Error GLTFDocument::_parse_cameras(Ref<GLTFState> p_state) { + if (!p_state->json.has("cameras")) { return OK; } - const Array cameras = state->json["cameras"]; + const Array cameras = p_state->json["cameras"]; for (GLTFCameraIndex i = 0; i < cameras.size(); i++) { - state->cameras.push_back(GLTFCamera::from_dictionary(cameras[i])); + p_state->cameras.push_back(GLTFCamera::from_dictionary(cameras[i])); } - print_verbose("glTF: Total cameras: " + itos(state->cameras.size())); + print_verbose("glTF: Total cameras: " + itos(p_state->cameras.size())); return OK; } @@ -4746,24 +4751,24 @@ String GLTFDocument::interpolation_to_string(const GLTFAnimation::Interpolation return interp; } -Error GLTFDocument::_serialize_animations(Ref<GLTFState> state) { - if (!state->animation_players.size()) { +Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) { + if (!p_state->animation_players.size()) { return OK; } - for (int32_t player_i = 0; player_i < state->animation_players.size(); player_i++) { + for (int32_t player_i = 0; player_i < p_state->animation_players.size(); player_i++) { List<StringName> animation_names; - AnimationPlayer *animation_player = state->animation_players[player_i]; + AnimationPlayer *animation_player = p_state->animation_players[player_i]; animation_player->get_animation_list(&animation_names); if (animation_names.size()) { for (int animation_name_i = 0; animation_name_i < animation_names.size(); animation_name_i++) { - _convert_animation(state, animation_player, animation_names[animation_name_i]); + _convert_animation(p_state, animation_player, animation_names[animation_name_i]); } } } Array animations; - for (GLTFAnimationIndex animation_i = 0; animation_i < state->animations.size(); animation_i++) { + for (GLTFAnimationIndex animation_i = 0; animation_i < p_state->animations.size(); animation_i++) { Dictionary d; - Ref<GLTFAnimation> gltf_animation = state->animations[animation_i]; + Ref<GLTFAnimation> gltf_animation = p_state->animations[animation_i]; if (!gltf_animation->get_tracks().size()) { continue; } @@ -4783,9 +4788,9 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> state) { s["interpolation"] = interpolation_to_string(track.position_track.interpolation); Vector<real_t> times = Variant(track.position_track.times); - s["input"] = _encode_accessor_as_floats(state, times, false); + s["input"] = _encode_accessor_as_floats(p_state, times, false); Vector<Vector3> values = Variant(track.position_track.values); - s["output"] = _encode_accessor_as_vec3(state, values, false); + s["output"] = _encode_accessor_as_vec3(p_state, values, false); samplers.push_back(s); @@ -4803,9 +4808,9 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> state) { s["interpolation"] = interpolation_to_string(track.rotation_track.interpolation); Vector<real_t> times = Variant(track.rotation_track.times); - s["input"] = _encode_accessor_as_floats(state, times, false); + s["input"] = _encode_accessor_as_floats(p_state, times, false); Vector<Quaternion> values = track.rotation_track.values; - s["output"] = _encode_accessor_as_quaternions(state, values, false); + s["output"] = _encode_accessor_as_quaternions(p_state, values, false); samplers.push_back(s); @@ -4823,9 +4828,9 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> state) { s["interpolation"] = interpolation_to_string(track.scale_track.interpolation); Vector<real_t> times = Variant(track.scale_track.times); - s["input"] = _encode_accessor_as_floats(state, times, false); + s["input"] = _encode_accessor_as_floats(p_state, times, false); Vector<Vector3> values = Variant(track.scale_track.values); - s["output"] = _encode_accessor_as_vec3(state, values, false); + s["output"] = _encode_accessor_as_vec3(p_state, values, false); samplers.push_back(s); @@ -4903,8 +4908,8 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> state) { } s["interpolation"] = interpolation_to_string(track.weight_tracks[track.weight_tracks.size() - 1].interpolation); - s["input"] = _encode_accessor_as_floats(state, all_track_times, false); - s["output"] = _encode_accessor_as_floats(state, all_track_values, false); + s["input"] = _encode_accessor_as_floats(p_state, all_track_times, false); + s["output"] = _encode_accessor_as_floats(p_state, all_track_values, false); samplers.push_back(s); @@ -4926,19 +4931,19 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> state) { if (!animations.size()) { return OK; } - state->json["animations"] = animations; + p_state->json["animations"] = animations; - print_verbose("glTF: Total animations '" + itos(state->animations.size()) + "'."); + print_verbose("glTF: Total animations '" + itos(p_state->animations.size()) + "'."); return OK; } -Error GLTFDocument::_parse_animations(Ref<GLTFState> state) { - if (!state->json.has("animations")) { +Error GLTFDocument::_parse_animations(Ref<GLTFState> p_state) { + if (!p_state->json.has("animations")) { return OK; } - const Array &animations = state->json["animations"]; + const Array &animations = p_state->json["animations"]; for (GLTFAnimationIndex i = 0; i < animations.size(); i++) { const Dictionary &d = animations[i]; @@ -4959,7 +4964,7 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> state) { if (anim_name_lower.begins_with("loop") || anim_name_lower.ends_with("loop") || anim_name_lower.begins_with("cycle") || anim_name_lower.ends_with("cycle")) { animation->set_loop(true); } - animation->set_name(_gen_unique_animation_name(state, anim_name)); + animation->set_name(_gen_unique_animation_name(p_state, anim_name)); } for (int j = 0; j < channels.size(); j++) { @@ -4980,7 +4985,7 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> state) { GLTFNodeIndex node = t["node"]; String path = t["path"]; - ERR_FAIL_INDEX_V(node, state->nodes.size(), ERR_PARSE_ERROR); + ERR_FAIL_INDEX_V(node, p_state->nodes.size(), ERR_PARSE_ERROR); GLTFAnimation::Track *track = nullptr; @@ -5015,27 +5020,27 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> state) { } } - const Vector<float> times = _decode_accessor_as_floats(state, input, false); + const Vector<float> times = _decode_accessor_as_floats(p_state, input, false); if (path == "translation") { - const Vector<Vector3> positions = _decode_accessor_as_vec3(state, output, false); + const Vector<Vector3> positions = _decode_accessor_as_vec3(p_state, output, false); track->position_track.interpolation = interp; track->position_track.times = Variant(times); //convert via variant track->position_track.values = Variant(positions); //convert via variant } else if (path == "rotation") { - const Vector<Quaternion> rotations = _decode_accessor_as_quaternion(state, output, false); + const Vector<Quaternion> rotations = _decode_accessor_as_quaternion(p_state, output, false); track->rotation_track.interpolation = interp; track->rotation_track.times = Variant(times); //convert via variant track->rotation_track.values = rotations; } else if (path == "scale") { - const Vector<Vector3> scales = _decode_accessor_as_vec3(state, output, false); + const Vector<Vector3> scales = _decode_accessor_as_vec3(p_state, output, false); track->scale_track.interpolation = interp; track->scale_track.times = Variant(times); //convert via variant track->scale_track.values = Variant(scales); //convert via variant } else if (path == "weights") { - const Vector<float> weights = _decode_accessor_as_floats(state, output, false); + const Vector<float> weights = _decode_accessor_as_floats(p_state, output, false); - ERR_FAIL_INDEX_V(state->nodes[node]->mesh, state->meshes.size(), ERR_PARSE_ERROR); - Ref<GLTFMesh> mesh = state->meshes[state->nodes[node]->mesh]; + ERR_FAIL_INDEX_V(p_state->nodes[node]->mesh, p_state->meshes.size(), ERR_PARSE_ERROR); + Ref<GLTFMesh> mesh = p_state->meshes[p_state->nodes[node]->mesh]; ERR_CONTINUE(!mesh->get_blend_weights().size()); const int wc = mesh->get_blend_weights().size(); @@ -5063,17 +5068,17 @@ Error GLTFDocument::_parse_animations(Ref<GLTFState> state) { } } - state->animations.push_back(animation); + p_state->animations.push_back(animation); } - print_verbose("glTF: Total animations '" + itos(state->animations.size()) + "'."); + print_verbose("glTF: Total animations '" + itos(p_state->animations.size()) + "'."); return OK; } -void GLTFDocument::_assign_scene_names(Ref<GLTFState> state) { - for (int i = 0; i < state->nodes.size(); i++) { - Ref<GLTFNode> n = state->nodes[i]; +void GLTFDocument::_assign_scene_names(Ref<GLTFState> p_state) { + for (int i = 0; i < p_state->nodes.size(); i++) { + Ref<GLTFNode> n = p_state->nodes[i]; // Any joints get unique names generated when the skeleton is made, unique to the skeleton if (n->skeleton >= 0) { @@ -5082,21 +5087,21 @@ void GLTFDocument::_assign_scene_names(Ref<GLTFState> state) { if (n->get_name().is_empty()) { if (n->mesh >= 0) { - n->set_name(_gen_unique_name(state, "Mesh")); + n->set_name(_gen_unique_name(p_state, "Mesh")); } else if (n->camera >= 0) { - n->set_name(_gen_unique_name(state, "Camera3D")); + n->set_name(_gen_unique_name(p_state, "Camera3D")); } else { - n->set_name(_gen_unique_name(state, "Node")); + n->set_name(_gen_unique_name(p_state, "Node")); } } - n->set_name(_gen_unique_name(state, n->get_name())); + n->set_name(_gen_unique_name(p_state, n->get_name())); } } -BoneAttachment3D *GLTFDocument::_generate_bone_attachment(Ref<GLTFState> state, Skeleton3D *skeleton, const GLTFNodeIndex node_index, const GLTFNodeIndex bone_index) { - Ref<GLTFNode> gltf_node = state->nodes[node_index]; - Ref<GLTFNode> bone_node = state->nodes[bone_index]; +BoneAttachment3D *GLTFDocument::_generate_bone_attachment(Ref<GLTFState> p_state, Skeleton3D *p_skeleton, const GLTFNodeIndex p_node_index, const GLTFNodeIndex p_bone_index) { + Ref<GLTFNode> gltf_node = p_state->nodes[p_node_index]; + Ref<GLTFNode> bone_node = p_state->nodes[p_bone_index]; BoneAttachment3D *bone_attachment = memnew(BoneAttachment3D); print_verbose("glTF: Creating bone attachment for: " + gltf_node->get_name()); @@ -5107,7 +5112,7 @@ BoneAttachment3D *GLTFDocument::_generate_bone_attachment(Ref<GLTFState> state, return bone_attachment; } -GLTFMeshIndex GLTFDocument::_convert_mesh_to_gltf(Ref<GLTFState> state, MeshInstance3D *p_mesh_instance) { +GLTFMeshIndex GLTFDocument::_convert_mesh_to_gltf(Ref<GLTFState> p_state, MeshInstance3D *p_mesh_instance) { ERR_FAIL_NULL_V(p_mesh_instance, -1); if (p_mesh_instance->get_mesh().is_null()) { return -1; @@ -5138,20 +5143,20 @@ GLTFMeshIndex GLTFDocument::_convert_mesh_to_gltf(Ref<GLTFState> state, MeshInst gltf_mesh->set_instance_materials(instance_materials); gltf_mesh->set_mesh(current_mesh); gltf_mesh->set_blend_weights(blend_weights); - GLTFMeshIndex mesh_i = state->meshes.size(); - state->meshes.push_back(gltf_mesh); + GLTFMeshIndex mesh_i = p_state->meshes.size(); + p_state->meshes.push_back(gltf_mesh); return mesh_i; } -ImporterMeshInstance3D *GLTFDocument::_generate_mesh_instance(Ref<GLTFState> state, const GLTFNodeIndex node_index) { - Ref<GLTFNode> gltf_node = state->nodes[node_index]; +ImporterMeshInstance3D *GLTFDocument::_generate_mesh_instance(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index) { + Ref<GLTFNode> gltf_node = p_state->nodes[p_node_index]; - ERR_FAIL_INDEX_V(gltf_node->mesh, state->meshes.size(), nullptr); + ERR_FAIL_INDEX_V(gltf_node->mesh, p_state->meshes.size(), nullptr); ImporterMeshInstance3D *mi = memnew(ImporterMeshInstance3D); print_verbose("glTF: Creating mesh for: " + gltf_node->get_name()); - Ref<GLTFMesh> mesh = state->meshes.write[gltf_node->mesh]; + Ref<GLTFMesh> mesh = p_state->meshes.write[gltf_node->mesh]; if (mesh.is_null()) { return mi; } @@ -5163,56 +5168,56 @@ ImporterMeshInstance3D *GLTFDocument::_generate_mesh_instance(Ref<GLTFState> sta return mi; } -Light3D *GLTFDocument::_generate_light(Ref<GLTFState> state, const GLTFNodeIndex node_index) { - Ref<GLTFNode> gltf_node = state->nodes[node_index]; +Light3D *GLTFDocument::_generate_light(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index) { + Ref<GLTFNode> gltf_node = p_state->nodes[p_node_index]; - ERR_FAIL_INDEX_V(gltf_node->light, state->lights.size(), nullptr); + ERR_FAIL_INDEX_V(gltf_node->light, p_state->lights.size(), nullptr); print_verbose("glTF: Creating light for: " + gltf_node->get_name()); - Ref<GLTFLight> l = state->lights[gltf_node->light]; + Ref<GLTFLight> l = p_state->lights[gltf_node->light]; return l->to_node(); } -Camera3D *GLTFDocument::_generate_camera(Ref<GLTFState> state, const GLTFNodeIndex node_index) { - Ref<GLTFNode> gltf_node = state->nodes[node_index]; +Camera3D *GLTFDocument::_generate_camera(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index) { + Ref<GLTFNode> gltf_node = p_state->nodes[p_node_index]; - ERR_FAIL_INDEX_V(gltf_node->camera, state->cameras.size(), nullptr); + ERR_FAIL_INDEX_V(gltf_node->camera, p_state->cameras.size(), nullptr); print_verbose("glTF: Creating camera for: " + gltf_node->get_name()); - Ref<GLTFCamera> c = state->cameras[gltf_node->camera]; + Ref<GLTFCamera> c = p_state->cameras[gltf_node->camera]; return c->to_node(); } -GLTFCameraIndex GLTFDocument::_convert_camera(Ref<GLTFState> state, Camera3D *p_camera) { +GLTFCameraIndex GLTFDocument::_convert_camera(Ref<GLTFState> p_state, Camera3D *p_camera) { print_verbose("glTF: Converting camera: " + p_camera->get_name()); Ref<GLTFCamera> c = GLTFCamera::from_node(p_camera); - GLTFCameraIndex camera_index = state->cameras.size(); - state->cameras.push_back(c); + GLTFCameraIndex camera_index = p_state->cameras.size(); + p_state->cameras.push_back(c); return camera_index; } -GLTFLightIndex GLTFDocument::_convert_light(Ref<GLTFState> state, Light3D *p_light) { +GLTFLightIndex GLTFDocument::_convert_light(Ref<GLTFState> p_state, Light3D *p_light) { print_verbose("glTF: Converting light: " + p_light->get_name()); Ref<GLTFLight> l = GLTFLight::from_node(p_light); - GLTFLightIndex light_index = state->lights.size(); - state->lights.push_back(l); + GLTFLightIndex light_index = p_state->lights.size(); + p_state->lights.push_back(l); return light_index; } -void GLTFDocument::_convert_spatial(Ref<GLTFState> state, Node3D *p_spatial, Ref<GLTFNode> p_node) { +void GLTFDocument::_convert_spatial(Ref<GLTFState> p_state, Node3D *p_spatial, Ref<GLTFNode> p_node) { Transform3D xform = p_spatial->get_transform(); p_node->scale = xform.basis.get_scale(); p_node->rotation = xform.basis.get_rotation_quaternion(); p_node->position = xform.origin; } -Node3D *GLTFDocument::_generate_spatial(Ref<GLTFState> state, const GLTFNodeIndex node_index) { - Ref<GLTFNode> gltf_node = state->nodes[node_index]; +Node3D *GLTFDocument::_generate_spatial(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index) { + Ref<GLTFNode> gltf_node = p_state->nodes[p_node_index]; Node3D *spatial = memnew(Node3D); print_verbose("glTF: Converting spatial: " + gltf_node->get_name()); @@ -5220,7 +5225,7 @@ Node3D *GLTFDocument::_generate_spatial(Ref<GLTFState> state, const GLTFNodeInde return spatial; } -void GLTFDocument::_convert_scene_node(Ref<GLTFState> state, Node *p_current, const GLTFNodeIndex p_gltf_parent, const GLTFNodeIndex p_gltf_root) { +void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current, const GLTFNodeIndex p_gltf_parent, const GLTFNodeIndex p_gltf_root) { bool retflag = true; _check_visibility(p_current, retflag); if (retflag) { @@ -5228,64 +5233,68 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> state, Node *p_current, co } Ref<GLTFNode> gltf_node; gltf_node.instantiate(); - gltf_node->set_name(_gen_unique_name(state, p_current->get_name())); + gltf_node->set_name(_gen_unique_name(p_state, p_current->get_name())); if (cast_to<Node3D>(p_current)) { Node3D *spatial = cast_to<Node3D>(p_current); - _convert_spatial(state, spatial, gltf_node); + _convert_spatial(p_state, spatial, gltf_node); } if (cast_to<MeshInstance3D>(p_current)) { MeshInstance3D *mi = cast_to<MeshInstance3D>(p_current); - _convert_mesh_instance_to_gltf(mi, state, gltf_node); + _convert_mesh_instance_to_gltf(mi, p_state, gltf_node); } else if (cast_to<BoneAttachment3D>(p_current)) { BoneAttachment3D *bone = cast_to<BoneAttachment3D>(p_current); - _convert_bone_attachment_to_gltf(bone, state, p_gltf_parent, p_gltf_root, gltf_node); + _convert_bone_attachment_to_gltf(bone, p_state, p_gltf_parent, p_gltf_root, gltf_node); return; } else if (cast_to<Skeleton3D>(p_current)) { Skeleton3D *skel = cast_to<Skeleton3D>(p_current); - _convert_skeleton_to_gltf(skel, state, p_gltf_parent, p_gltf_root, gltf_node); + _convert_skeleton_to_gltf(skel, p_state, p_gltf_parent, p_gltf_root, gltf_node); // We ignore the Godot Engine node that is the skeleton. return; } else if (cast_to<MultiMeshInstance3D>(p_current)) { MultiMeshInstance3D *multi = cast_to<MultiMeshInstance3D>(p_current); - _convert_multi_mesh_instance_to_gltf(multi, p_gltf_parent, p_gltf_root, gltf_node, state); + _convert_multi_mesh_instance_to_gltf(multi, p_gltf_parent, p_gltf_root, gltf_node, p_state); #ifdef MODULE_CSG_ENABLED } else if (cast_to<CSGShape3D>(p_current)) { CSGShape3D *shape = cast_to<CSGShape3D>(p_current); if (shape->get_parent() && shape->is_root_shape()) { - _convert_csg_shape_to_gltf(shape, p_gltf_parent, gltf_node, state); + _convert_csg_shape_to_gltf(shape, p_gltf_parent, gltf_node, p_state); } #endif // MODULE_CSG_ENABLED #ifdef MODULE_GRIDMAP_ENABLED } else if (cast_to<GridMap>(p_current)) { GridMap *gridmap = Object::cast_to<GridMap>(p_current); - _convert_grid_map_to_gltf(gridmap, p_gltf_parent, p_gltf_root, gltf_node, state); + _convert_grid_map_to_gltf(gridmap, p_gltf_parent, p_gltf_root, gltf_node, p_state); #endif // MODULE_GRIDMAP_ENABLED } else if (cast_to<Camera3D>(p_current)) { Camera3D *camera = Object::cast_to<Camera3D>(p_current); - _convert_camera_to_gltf(camera, state, gltf_node); + _convert_camera_to_gltf(camera, p_state, gltf_node); } else if (cast_to<Light3D>(p_current)) { Light3D *light = Object::cast_to<Light3D>(p_current); - _convert_light_to_gltf(light, state, gltf_node); + _convert_light_to_gltf(light, p_state, gltf_node); } else if (cast_to<AnimationPlayer>(p_current)) { AnimationPlayer *animation_player = Object::cast_to<AnimationPlayer>(p_current); - _convert_animation_player_to_gltf(animation_player, state, p_gltf_parent, p_gltf_root, gltf_node, p_current); + _convert_animation_player_to_gltf(animation_player, p_state, p_gltf_parent, p_gltf_root, gltf_node, p_current); + } + for (Ref<GLTFDocumentExtension> ext : document_extensions) { + ERR_CONTINUE(ext.is_null()); + ext->convert_scene_node(p_state, gltf_node, p_current); } - GLTFNodeIndex current_node_i = state->nodes.size(); + GLTFNodeIndex current_node_i = p_state->nodes.size(); GLTFNodeIndex gltf_root = p_gltf_root; if (gltf_root == -1) { gltf_root = current_node_i; Array scenes; scenes.push_back(gltf_root); - state->json["scene"] = scenes; + p_state->json["scene"] = scenes; } - _create_gltf_node(state, p_current, current_node_i, p_gltf_parent, gltf_root, gltf_node); + _create_gltf_node(p_state, p_current, current_node_i, p_gltf_parent, gltf_root, gltf_node); for (int node_i = 0; node_i < p_current->get_child_count(); node_i++) { - _convert_scene_node(state, p_current->get_child(node_i), current_node_i, gltf_root); + _convert_scene_node(p_state, p_current->get_child(node_i), current_node_i, gltf_root); } } #ifdef MODULE_CSG_ENABLED -void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeIndex p_gltf_parent, Ref<GLTFNode> gltf_node, Ref<GLTFState> state) { +void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeIndex p_gltf_parent, Ref<GLTFNode> p_gltf_node, Ref<GLTFState> p_state) { CSGShape3D *csg = p_current; csg->call("_update_shape"); Array meshes = csg->get_meshes(); @@ -5317,34 +5326,34 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd Ref<GLTFMesh> gltf_mesh; gltf_mesh.instantiate(); gltf_mesh->set_mesh(mesh); - GLTFMeshIndex mesh_i = state->meshes.size(); - state->meshes.push_back(gltf_mesh); - gltf_node->mesh = mesh_i; - gltf_node->xform = csg->get_meshes()[0]; - gltf_node->set_name(_gen_unique_name(state, csg->get_name())); + GLTFMeshIndex mesh_i = p_state->meshes.size(); + p_state->meshes.push_back(gltf_mesh); + p_gltf_node->mesh = mesh_i; + p_gltf_node->xform = csg->get_meshes()[0]; + p_gltf_node->set_name(_gen_unique_name(p_state, csg->get_name())); } #endif // MODULE_CSG_ENABLED -void GLTFDocument::_create_gltf_node(Ref<GLTFState> state, Node *p_scene_parent, GLTFNodeIndex current_node_i, - GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_gltf_node, Ref<GLTFNode> gltf_node) { - state->scene_nodes.insert(current_node_i, p_scene_parent); - state->nodes.push_back(gltf_node); - ERR_FAIL_COND(current_node_i == p_parent_node_index); - state->nodes.write[current_node_i]->parent = p_parent_node_index; +void GLTFDocument::_create_gltf_node(Ref<GLTFState> p_state, Node *p_scene_parent, GLTFNodeIndex p_current_node_i, + GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_gltf_node, Ref<GLTFNode> p_gltf_node) { + p_state->scene_nodes.insert(p_current_node_i, p_scene_parent); + p_state->nodes.push_back(p_gltf_node); + ERR_FAIL_COND(p_current_node_i == p_parent_node_index); + p_state->nodes.write[p_current_node_i]->parent = p_parent_node_index; if (p_parent_node_index == -1) { return; } - state->nodes.write[p_parent_node_index]->children.push_back(current_node_i); + p_state->nodes.write[p_parent_node_index]->children.push_back(p_current_node_i); } -void GLTFDocument::_convert_animation_player_to_gltf(AnimationPlayer *animation_player, Ref<GLTFState> state, GLTFNodeIndex p_gltf_current, GLTFNodeIndex p_gltf_root_index, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) { - ERR_FAIL_COND(!animation_player); - state->animation_players.push_back(animation_player); - print_verbose(String("glTF: Converting animation player: ") + animation_player->get_name()); +void GLTFDocument::_convert_animation_player_to_gltf(AnimationPlayer *p_animation_player, Ref<GLTFState> p_state, GLTFNodeIndex p_gltf_current, GLTFNodeIndex p_gltf_root_index, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) { + ERR_FAIL_COND(!p_animation_player); + p_state->animation_players.push_back(p_animation_player); + print_verbose(String("glTF: Converting animation player: ") + p_animation_player->get_name()); } -void GLTFDocument::_check_visibility(Node *p_node, bool &retflag) { - retflag = true; +void GLTFDocument::_check_visibility(Node *p_node, bool &r_retflag) { + r_retflag = true; Node3D *spatial = Object::cast_to<Node3D>(p_node); Node2D *node_2d = Object::cast_to<Node2D>(p_node); if (node_2d && !node_2d->is_visible()) { @@ -5353,32 +5362,32 @@ void GLTFDocument::_check_visibility(Node *p_node, bool &retflag) { if (spatial && !spatial->is_visible()) { return; } - retflag = false; + r_retflag = false; } -void GLTFDocument::_convert_camera_to_gltf(Camera3D *camera, Ref<GLTFState> state, Ref<GLTFNode> gltf_node) { +void GLTFDocument::_convert_camera_to_gltf(Camera3D *camera, Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node) { ERR_FAIL_COND(!camera); - GLTFCameraIndex camera_index = _convert_camera(state, camera); + GLTFCameraIndex camera_index = _convert_camera(p_state, camera); if (camera_index != -1) { - gltf_node->camera = camera_index; + p_gltf_node->camera = camera_index; } } -void GLTFDocument::_convert_light_to_gltf(Light3D *light, Ref<GLTFState> state, Ref<GLTFNode> gltf_node) { +void GLTFDocument::_convert_light_to_gltf(Light3D *light, Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node) { ERR_FAIL_COND(!light); - GLTFLightIndex light_index = _convert_light(state, light); + GLTFLightIndex light_index = _convert_light(p_state, light); if (light_index != -1) { - gltf_node->light = light_index; + p_gltf_node->light = light_index; } } #ifdef MODULE_GRIDMAP_ENABLED -void GLTFDocument::_convert_grid_map_to_gltf(GridMap *p_grid_map, GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_node_index, Ref<GLTFNode> gltf_node, Ref<GLTFState> state) { +void GLTFDocument::_convert_grid_map_to_gltf(GridMap *p_grid_map, GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_node_index, Ref<GLTFNode> p_gltf_node, Ref<GLTFState> p_state) { Array cells = p_grid_map->get_used_cells(); for (int32_t k = 0; k < cells.size(); k++) { GLTFNode *new_gltf_node = memnew(GLTFNode); - gltf_node->children.push_back(state->nodes.size()); - state->nodes.push_back(new_gltf_node); + p_gltf_node->children.push_back(p_state->nodes.size()); + p_state->nodes.push_back(new_gltf_node); Vector3 cell_location = cells[k]; int32_t cell = p_grid_map->get_cell_item( Vector3(cell_location.x, cell_location.y, cell_location.z)); @@ -5394,10 +5403,10 @@ void GLTFDocument::_convert_grid_map_to_gltf(GridMap *p_grid_map, GLTFNodeIndex Ref<GLTFMesh> gltf_mesh; gltf_mesh.instantiate(); gltf_mesh->set_mesh(_mesh_to_importer_mesh(p_grid_map->get_mesh_library()->get_item_mesh(cell))); - new_gltf_node->mesh = state->meshes.size(); - state->meshes.push_back(gltf_mesh); + new_gltf_node->mesh = p_state->meshes.size(); + p_state->meshes.push_back(gltf_mesh); new_gltf_node->xform = cell_xform * p_grid_map->get_transform(); - new_gltf_node->set_name(_gen_unique_name(state, p_grid_map->get_mesh_library()->get_item_name(cell))); + new_gltf_node->set_name(_gen_unique_name(p_state, p_grid_map->get_mesh_library()->get_item_name(cell))); } } #endif // MODULE_GRIDMAP_ENABLED @@ -5406,7 +5415,7 @@ void GLTFDocument::_convert_multi_mesh_instance_to_gltf( MultiMeshInstance3D *p_multi_mesh_instance, GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_node_index, - Ref<GLTFNode> gltf_node, Ref<GLTFState> state) { + Ref<GLTFNode> p_gltf_node, Ref<GLTFState> p_state) { ERR_FAIL_COND(!p_multi_mesh_instance); Ref<MultiMesh> multi_mesh = p_multi_mesh_instance->get_multimesh(); if (multi_mesh.is_null()) { @@ -5442,8 +5451,8 @@ void GLTFDocument::_convert_multi_mesh_instance_to_gltf( blend_arrays, mesh->surface_get_lods(surface_i), mat, material_name, mesh->surface_get_format(surface_i)); } gltf_mesh->set_mesh(importer_mesh); - GLTFMeshIndex mesh_index = state->meshes.size(); - state->meshes.push_back(gltf_mesh); + GLTFMeshIndex mesh_index = p_state->meshes.size(); + p_state->meshes.push_back(gltf_mesh); for (int32_t instance_i = 0; instance_i < multi_mesh->get_instance_count(); instance_i++) { Transform3D transform; @@ -5465,22 +5474,22 @@ void GLTFDocument::_convert_multi_mesh_instance_to_gltf( new_gltf_node.instantiate(); new_gltf_node->mesh = mesh_index; new_gltf_node->xform = transform; - new_gltf_node->set_name(_gen_unique_name(state, p_multi_mesh_instance->get_name())); - gltf_node->children.push_back(state->nodes.size()); - state->nodes.push_back(new_gltf_node); + new_gltf_node->set_name(_gen_unique_name(p_state, p_multi_mesh_instance->get_name())); + p_gltf_node->children.push_back(p_state->nodes.size()); + p_state->nodes.push_back(new_gltf_node); } } -void GLTFDocument::_convert_skeleton_to_gltf(Skeleton3D *p_skeleton3d, Ref<GLTFState> state, GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_node_index, Ref<GLTFNode> gltf_node) { +void GLTFDocument::_convert_skeleton_to_gltf(Skeleton3D *p_skeleton3d, Ref<GLTFState> p_state, GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_node_index, Ref<GLTFNode> p_gltf_node) { Skeleton3D *skeleton = p_skeleton3d; Ref<GLTFSkeleton> gltf_skeleton; gltf_skeleton.instantiate(); - // GLTFSkeleton is only used to hold internal state data. It will not be written to the document. + // GLTFSkeleton is only used to hold internal p_state data. It will not be written to the document. // gltf_skeleton->godot_skeleton = skeleton; - GLTFSkeletonIndex skeleton_i = state->skeletons.size(); - state->skeleton3d_to_gltf_skeleton[skeleton->get_instance_id()] = skeleton_i; - state->skeletons.push_back(gltf_skeleton); + GLTFSkeletonIndex skeleton_i = p_state->skeletons.size(); + p_state->skeleton3d_to_gltf_skeleton[skeleton->get_instance_id()] = skeleton_i; + p_state->skeletons.push_back(gltf_skeleton); BoneId bone_count = skeleton->get_bone_count(); for (BoneId bone_i = 0; bone_i < bone_count; bone_i++) { @@ -5488,15 +5497,15 @@ void GLTFDocument::_convert_skeleton_to_gltf(Skeleton3D *p_skeleton3d, Ref<GLTFS joint_node.instantiate(); // Note that we cannot use _gen_unique_bone_name here, because glTF spec requires all node // names to be unique regardless of whether or not they are used as joints. - joint_node->set_name(_gen_unique_name(state, skeleton->get_bone_name(bone_i))); + joint_node->set_name(_gen_unique_name(p_state, skeleton->get_bone_name(bone_i))); Transform3D xform = skeleton->get_bone_pose(bone_i); joint_node->scale = xform.basis.get_scale(); joint_node->rotation = xform.basis.get_rotation_quaternion(); joint_node->position = xform.origin; joint_node->joint = true; - GLTFNodeIndex current_node_i = state->nodes.size(); - state->scene_nodes.insert(current_node_i, skeleton); - state->nodes.push_back(joint_node); + GLTFNodeIndex current_node_i = p_state->nodes.size(); + p_state->scene_nodes.insert(current_node_i, skeleton); + p_state->nodes.push_back(joint_node); gltf_skeleton->joints.push_back(current_node_i); if (skeleton->get_bone_parent(bone_i) == -1) { @@ -5509,23 +5518,23 @@ void GLTFDocument::_convert_skeleton_to_gltf(Skeleton3D *p_skeleton3d, Ref<GLTFS BoneId parent_bone_id = skeleton->get_bone_parent(bone_i); if (parent_bone_id == -1) { if (p_parent_node_index != -1) { - state->nodes.write[current_node_i]->parent = p_parent_node_index; - state->nodes.write[p_parent_node_index]->children.push_back(current_node_i); + p_state->nodes.write[current_node_i]->parent = p_parent_node_index; + p_state->nodes.write[p_parent_node_index]->children.push_back(current_node_i); } } else { GLTFNodeIndex parent_node_i = gltf_skeleton->godot_bone_node[parent_bone_id]; - state->nodes.write[current_node_i]->parent = parent_node_i; - state->nodes.write[parent_node_i]->children.push_back(current_node_i); + p_state->nodes.write[current_node_i]->parent = parent_node_i; + p_state->nodes.write[parent_node_i]->children.push_back(current_node_i); } } // Remove placeholder skeleton3d node by not creating the gltf node // Skins are per mesh for (int node_i = 0; node_i < skeleton->get_child_count(); node_i++) { - _convert_scene_node(state, skeleton->get_child(node_i), p_parent_node_index, p_root_node_index); + _convert_scene_node(p_state, skeleton->get_child(node_i), p_parent_node_index, p_root_node_index); } } -void GLTFDocument::_convert_bone_attachment_to_gltf(BoneAttachment3D *p_bone_attachment, Ref<GLTFState> state, GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_node_index, Ref<GLTFNode> gltf_node) { +void GLTFDocument::_convert_bone_attachment_to_gltf(BoneAttachment3D *p_bone_attachment, Ref<GLTFState> p_state, GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_node_index, Ref<GLTFNode> p_gltf_node) { Skeleton3D *skeleton; // Note that relative transforms to external skeletons and pose overrides are not supported. if (p_bone_attachment->get_use_external_skeleton()) { @@ -5534,8 +5543,8 @@ void GLTFDocument::_convert_bone_attachment_to_gltf(BoneAttachment3D *p_bone_att skeleton = cast_to<Skeleton3D>(p_bone_attachment->get_parent()); } GLTFSkeletonIndex skel_gltf_i = -1; - if (skeleton != nullptr && state->skeleton3d_to_gltf_skeleton.has(skeleton->get_instance_id())) { - skel_gltf_i = state->skeleton3d_to_gltf_skeleton[skeleton->get_instance_id()]; + if (skeleton != nullptr && p_state->skeleton3d_to_gltf_skeleton.has(skeleton->get_instance_id())) { + skel_gltf_i = p_state->skeleton3d_to_gltf_skeleton[skeleton->get_instance_id()]; } int bone_idx = -1; if (skeleton != nullptr) { @@ -5546,28 +5555,28 @@ void GLTFDocument::_convert_bone_attachment_to_gltf(BoneAttachment3D *p_bone_att } GLTFNodeIndex par_node_index = p_parent_node_index; if (skeleton != nullptr && bone_idx != -1 && skel_gltf_i != -1) { - Ref<GLTFSkeleton> gltf_skeleton = state->skeletons.write[skel_gltf_i]; + Ref<GLTFSkeleton> gltf_skeleton = p_state->skeletons.write[skel_gltf_i]; gltf_skeleton->bone_attachments.push_back(p_bone_attachment); par_node_index = gltf_skeleton->joints[bone_idx]; } for (int node_i = 0; node_i < p_bone_attachment->get_child_count(); node_i++) { - _convert_scene_node(state, p_bone_attachment->get_child(node_i), par_node_index, p_root_node_index); + _convert_scene_node(p_state, p_bone_attachment->get_child(node_i), par_node_index, p_root_node_index); } } -void GLTFDocument::_convert_mesh_instance_to_gltf(MeshInstance3D *p_scene_parent, Ref<GLTFState> state, Ref<GLTFNode> gltf_node) { - GLTFMeshIndex gltf_mesh_index = _convert_mesh_to_gltf(state, p_scene_parent); +void GLTFDocument::_convert_mesh_instance_to_gltf(MeshInstance3D *p_scene_parent, Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node) { + GLTFMeshIndex gltf_mesh_index = _convert_mesh_to_gltf(p_state, p_scene_parent); if (gltf_mesh_index != -1) { - gltf_node->mesh = gltf_mesh_index; + p_gltf_node->mesh = gltf_mesh_index; } } -void GLTFDocument::_generate_scene_node(Ref<GLTFState> state, Node *scene_parent, Node3D *scene_root, const GLTFNodeIndex node_index) { - Ref<GLTFNode> gltf_node = state->nodes[node_index]; +void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, Node *scene_parent, Node3D *scene_root, const GLTFNodeIndex node_index) { + Ref<GLTFNode> gltf_node = p_state->nodes[node_index]; if (gltf_node->skeleton >= 0) { - _generate_skeleton_bone_node(state, scene_parent, scene_root, node_index); + _generate_skeleton_bone_node(p_state, scene_parent, scene_root, node_index); return; } @@ -5581,76 +5590,87 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> state, Node *scene_parent // skinned meshes must not be placed in a bone attachment. if (non_bone_parented_to_skeleton && gltf_node->skin < 0) { // Bone Attachment - Parent Case - BoneAttachment3D *bone_attachment = _generate_bone_attachment(state, active_skeleton, node_index, gltf_node->parent); + BoneAttachment3D *bone_attachment = _generate_bone_attachment(p_state, active_skeleton, node_index, gltf_node->parent); scene_parent->add_child(bone_attachment, true); bone_attachment->set_owner(scene_root); // There is no gltf_node that represent this, so just directly create a unique name - bone_attachment->set_name(_gen_unique_name(state, "BoneAttachment3D")); + bone_attachment->set_name(_gen_unique_name(p_state, "BoneAttachment3D")); // We change the scene_parent to our bone attachment now. We do not set current_node because we want to make the node // and attach it to the bone_attachment scene_parent = bone_attachment; } - if (gltf_node->mesh >= 0) { - current_node = _generate_mesh_instance(state, node_index); - } else if (gltf_node->camera >= 0) { - current_node = _generate_camera(state, node_index); - } else if (gltf_node->light >= 0) { - current_node = _generate_light(state, node_index); + // Check if any GLTFDocumentExtension classes want to generate a node for us. + for (Ref<GLTFDocumentExtension> ext : document_extensions) { + ERR_CONTINUE(ext.is_null()); + current_node = ext->generate_scene_node(p_state, gltf_node, scene_parent); + if (current_node) { + break; + } } - - // We still have not managed to make a node. + // If none of our GLTFDocumentExtension classes generated us a node, we generate one. if (!current_node) { - current_node = _generate_spatial(state, node_index); + if (gltf_node->mesh >= 0) { + current_node = _generate_mesh_instance(p_state, node_index); + } else if (gltf_node->camera >= 0) { + current_node = _generate_camera(p_state, node_index); + } else if (gltf_node->light >= 0) { + current_node = _generate_light(p_state, node_index); + } else { + current_node = _generate_spatial(p_state, node_index); + } } + // Add the node we generated and set the owner to the scene root. scene_parent->add_child(current_node, true); if (current_node != scene_root) { - current_node->set_owner(scene_root); + Array args; + args.append(scene_root); + current_node->propagate_call(StringName("set_owner"), args); } current_node->set_transform(gltf_node->xform); current_node->set_name(gltf_node->get_name()); - state->scene_nodes.insert(node_index, current_node); + p_state->scene_nodes.insert(node_index, current_node); for (int i = 0; i < gltf_node->children.size(); ++i) { - _generate_scene_node(state, current_node, scene_root, gltf_node->children[i]); + _generate_scene_node(p_state, current_node, scene_root, gltf_node->children[i]); } } -void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> state, Node *scene_parent, Node3D *scene_root, const GLTFNodeIndex node_index) { - Ref<GLTFNode> gltf_node = state->nodes[node_index]; +void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> p_state, Node *p_scene_parent, Node3D *p_scene_root, const GLTFNodeIndex p_node_index) { + Ref<GLTFNode> gltf_node = p_state->nodes[p_node_index]; Node3D *current_node = nullptr; - Skeleton3D *skeleton = state->skeletons[gltf_node->skeleton]->godot_skeleton; + Skeleton3D *skeleton = p_state->skeletons[gltf_node->skeleton]->godot_skeleton; // In this case, this node is already a bone in skeleton. const bool is_skinned_mesh = (gltf_node->skin >= 0 && gltf_node->mesh >= 0); const bool requires_extra_node = (gltf_node->mesh >= 0 || gltf_node->camera >= 0 || gltf_node->light >= 0); - Skeleton3D *active_skeleton = Object::cast_to<Skeleton3D>(scene_parent); + Skeleton3D *active_skeleton = Object::cast_to<Skeleton3D>(p_scene_parent); if (active_skeleton != skeleton) { if (active_skeleton) { // Bone Attachment - Direct Parented Skeleton Case - BoneAttachment3D *bone_attachment = _generate_bone_attachment(state, active_skeleton, node_index, gltf_node->parent); + BoneAttachment3D *bone_attachment = _generate_bone_attachment(p_state, active_skeleton, p_node_index, gltf_node->parent); - scene_parent->add_child(bone_attachment, true); - bone_attachment->set_owner(scene_root); + p_scene_parent->add_child(bone_attachment, true); + bone_attachment->set_owner(p_scene_root); // There is no gltf_node that represent this, so just directly create a unique name - bone_attachment->set_name(_gen_unique_name(state, "BoneAttachment3D")); + bone_attachment->set_name(_gen_unique_name(p_state, "BoneAttachment3D")); // We change the scene_parent to our bone attachment now. We do not set current_node because we want to make the node // and attach it to the bone_attachment - scene_parent = bone_attachment; - WARN_PRINT(vformat("glTF: Generating scene detected direct parented Skeletons at node %d", node_index)); + p_scene_parent = bone_attachment; + WARN_PRINT(vformat("glTF: Generating scene detected direct parented Skeletons at node %d", p_node_index)); } // Add it to the scene if it has not already been added if (skeleton->get_parent() == nullptr) { - scene_parent->add_child(skeleton, true); - skeleton->set_owner(scene_root); + p_scene_parent->add_child(skeleton, true); + skeleton->set_owner(p_scene_root); } } @@ -5661,40 +5681,53 @@ void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> state, Node *scen // skinned meshes must not be placed in a bone attachment. if (!is_skinned_mesh) { // Bone Attachment - Same Node Case - BoneAttachment3D *bone_attachment = _generate_bone_attachment(state, active_skeleton, node_index, node_index); + BoneAttachment3D *bone_attachment = _generate_bone_attachment(p_state, active_skeleton, p_node_index, p_node_index); - scene_parent->add_child(bone_attachment, true); - bone_attachment->set_owner(scene_root); + p_scene_parent->add_child(bone_attachment, true); + bone_attachment->set_owner(p_scene_root); // There is no gltf_node that represent this, so just directly create a unique name - bone_attachment->set_name(_gen_unique_name(state, "BoneAttachment3D")); + bone_attachment->set_name(_gen_unique_name(p_state, "BoneAttachment3D")); // We change the scene_parent to our bone attachment now. We do not set current_node because we want to make the node // and attach it to the bone_attachment - scene_parent = bone_attachment; + p_scene_parent = bone_attachment; } - - // We still have not managed to make a node - if (gltf_node->mesh >= 0) { - current_node = _generate_mesh_instance(state, node_index); - } else if (gltf_node->camera >= 0) { - current_node = _generate_camera(state, node_index); - } else if (gltf_node->light >= 0) { - current_node = _generate_light(state, node_index); + // Check if any GLTFDocumentExtension classes want to generate a node for us. + for (Ref<GLTFDocumentExtension> ext : document_extensions) { + ERR_CONTINUE(ext.is_null()); + current_node = ext->generate_scene_node(p_state, gltf_node, p_scene_parent); + if (current_node) { + break; + } + } + // If none of our GLTFDocumentExtension classes generated us a node, we generate one. + if (!current_node) { + if (gltf_node->mesh >= 0) { + current_node = _generate_mesh_instance(p_state, p_node_index); + } else if (gltf_node->camera >= 0) { + current_node = _generate_camera(p_state, p_node_index); + } else if (gltf_node->light >= 0) { + current_node = _generate_light(p_state, p_node_index); + } else { + current_node = _generate_spatial(p_state, p_node_index); + } } - - scene_parent->add_child(current_node, true); - if (current_node != scene_root) { - current_node->set_owner(scene_root); + // Add the node we generated and set the owner to the scene root. + p_scene_parent->add_child(current_node, true); + if (current_node != p_scene_root) { + Array args; + args.append(p_scene_root); + current_node->propagate_call(StringName("set_owner"), args); } // Do not set transform here. Transform is already applied to our bone. current_node->set_name(gltf_node->get_name()); } - state->scene_nodes.insert(node_index, current_node); + p_state->scene_nodes.insert(p_node_index, current_node); for (int i = 0; i < gltf_node->children.size(); ++i) { - _generate_scene_node(state, active_skeleton, scene_root, gltf_node->children[i]); + _generate_scene_node(p_state, active_skeleton, p_scene_root, gltf_node->children[i]); } } @@ -5819,13 +5852,13 @@ T GLTFDocument::_interpolate_track(const Vector<real_t> &p_times, const Vector<T ERR_FAIL_V(p_values[0]); } -void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, const GLTFAnimationIndex index, const int bake_fps) { - Ref<GLTFAnimation> anim = state->animations[index]; +void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, const GLTFAnimationIndex p_index, const float p_bake_fps, const bool p_trimming) { + Ref<GLTFAnimation> anim = p_state->animations[p_index]; String anim_name = anim->get_name(); if (anim_name.is_empty()) { // No node represent these, and they are not in the hierarchy, so just make a unique name - anim_name = _gen_unique_name(state, "Animation"); + anim_name = _gen_unique_name(p_state, "Animation"); } Ref<Animation> animation; @@ -5836,7 +5869,8 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, animation->set_loop_mode(Animation::LOOP_LINEAR); } - float length = 0.0; + double anim_start = p_trimming ? INFINITY : 0.0; + double anim_end = 0.0; for (const KeyValue<int, GLTFAnimation::Track> &track_i : anim->get_tracks()) { const GLTFAnimation::Track &track = track_i.value; @@ -5847,38 +5881,59 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, GLTFNodeIndex node_index = track_i.key; - const Ref<GLTFNode> gltf_node = state->nodes[track_i.key]; + const Ref<GLTFNode> gltf_node = p_state->nodes[track_i.key]; - Node *root = ap->get_parent(); + Node *root = p_animation_player->get_parent(); ERR_FAIL_COND(root == nullptr); - HashMap<GLTFNodeIndex, Node *>::Iterator node_element = state->scene_nodes.find(node_index); + HashMap<GLTFNodeIndex, Node *>::Iterator node_element = p_state->scene_nodes.find(node_index); ERR_CONTINUE_MSG(!node_element, vformat("Unable to find node %d for animation", node_index)); node_path = root->get_path_to(node_element->value); if (gltf_node->skeleton >= 0) { - const Skeleton3D *sk = state->skeletons[gltf_node->skeleton]->godot_skeleton; + const Skeleton3D *sk = p_state->skeletons[gltf_node->skeleton]->godot_skeleton; ERR_FAIL_COND(sk == nullptr); - const String path = ap->get_parent()->get_path_to(sk); + const String path = p_animation_player->get_parent()->get_path_to(sk); const String bone = gltf_node->get_name(); transform_node_path = path + ":" + bone; } else { transform_node_path = node_path; } - for (int i = 0; i < track.rotation_track.times.size(); i++) { - length = MAX(length, track.rotation_track.times[i]); - } - for (int i = 0; i < track.position_track.times.size(); i++) { - length = MAX(length, track.position_track.times[i]); - } - for (int i = 0; i < track.scale_track.times.size(); i++) { - length = MAX(length, track.scale_track.times[i]); - } - - for (int i = 0; i < track.weight_tracks.size(); i++) { - for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { - length = MAX(length, track.weight_tracks[i].times[j]); + if (p_trimming) { + for (int i = 0; i < track.rotation_track.times.size(); i++) { + anim_start = MIN(anim_start, track.rotation_track.times[i]); + anim_end = MAX(anim_end, track.rotation_track.times[i]); + } + for (int i = 0; i < track.position_track.times.size(); i++) { + anim_start = MIN(anim_start, track.position_track.times[i]); + anim_end = MAX(anim_end, track.position_track.times[i]); + } + for (int i = 0; i < track.scale_track.times.size(); i++) { + anim_start = MIN(anim_start, track.scale_track.times[i]); + anim_end = MAX(anim_end, track.scale_track.times[i]); + } + for (int i = 0; i < track.weight_tracks.size(); i++) { + for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { + anim_start = MIN(anim_start, track.weight_tracks[i].times[j]); + anim_end = MAX(anim_end, track.weight_tracks[i].times[j]); + } + } + } else { + // If you don't use trimming and the first key time is not at 0.0, fake keys will be inserted. + for (int i = 0; i < track.rotation_track.times.size(); i++) { + anim_end = MAX(anim_end, track.rotation_track.times[i]); + } + for (int i = 0; i < track.position_track.times.size(); i++) { + anim_end = MAX(anim_end, track.position_track.times[i]); + } + for (int i = 0; i < track.scale_track.times.size(); i++) { + anim_end = MAX(anim_end, track.scale_track.times[i]); + } + for (int i = 0; i < track.weight_tracks.size(); i++) { + for (int j = 0; j < track.weight_tracks[i].times.size(); j++) { + anim_end = MAX(anim_end, track.weight_tracks[i].times[j]); + } } } @@ -5892,7 +5947,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, int scale_idx = -1; if (track.position_track.values.size()) { - Vector3 base_pos = state->nodes[track_i.key]->position; + Vector3 base_pos = p_state->nodes[track_i.key]->position; bool not_default = false; //discard the track if all it contains is default values for (int i = 0; i < track.position_track.times.size(); i++) { Vector3 value = track.position_track.values[track.position_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i]; @@ -5911,7 +5966,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, } } if (track.rotation_track.values.size()) { - Quaternion base_rot = state->nodes[track_i.key]->rotation.normalized(); + Quaternion base_rot = p_state->nodes[track_i.key]->rotation.normalized(); bool not_default = false; //discard the track if all it contains is default values for (int i = 0; i < track.rotation_track.times.size(); i++) { Quaternion value = track.rotation_track.values[track.rotation_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i].normalized(); @@ -5929,7 +5984,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, } } if (track.scale_track.values.size()) { - Vector3 base_scale = state->nodes[track_i.key]->scale; + Vector3 base_scale = p_state->nodes[track_i.key]->scale; bool not_default = false; //discard the track if all it contains is default values for (int i = 0; i < track.scale_track.times.size(); i++) { Vector3 value = track.scale_track.values[track.scale_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i]; @@ -5947,25 +6002,23 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, } } - //first determine animation length - - const double increment = 1.0 / bake_fps; - double time = 0.0; + const double increment = 1.0 / p_bake_fps; + double time = anim_start; Vector3 base_pos; Quaternion base_rot; Vector3 base_scale = Vector3(1, 1, 1); if (rotation_idx == -1) { - base_rot = state->nodes[track_i.key]->rotation.normalized(); + base_rot = p_state->nodes[track_i.key]->rotation.normalized(); } if (position_idx == -1) { - base_pos = state->nodes[track_i.key]->position; + base_pos = p_state->nodes[track_i.key]->position; } if (scale_idx == -1) { - base_scale = state->nodes[track_i.key]->scale; + base_scale = p_state->nodes[track_i.key]->scale; } bool last = false; @@ -5976,33 +6029,33 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, if (position_idx >= 0) { pos = _interpolate_track<Vector3>(track.position_track.times, track.position_track.values, time, track.position_track.interpolation); - animation->position_track_insert_key(position_idx, time, pos); + animation->position_track_insert_key(position_idx, time - anim_start, pos); } if (rotation_idx >= 0) { rot = _interpolate_track<Quaternion>(track.rotation_track.times, track.rotation_track.values, time, track.rotation_track.interpolation); - animation->rotation_track_insert_key(rotation_idx, time, rot); + animation->rotation_track_insert_key(rotation_idx, time - anim_start, rot); } if (scale_idx >= 0) { scale = _interpolate_track<Vector3>(track.scale_track.times, track.scale_track.values, time, track.scale_track.interpolation); - animation->scale_track_insert_key(scale_idx, time, scale); + animation->scale_track_insert_key(scale_idx, time - anim_start, scale); } if (last) { break; } time += increment; - if (time >= length) { + if (time >= anim_end) { last = true; - time = length; + time = anim_end; } } } for (int i = 0; i < track.weight_tracks.size(); i++) { - ERR_CONTINUE(gltf_node->mesh < 0 || gltf_node->mesh >= state->meshes.size()); - Ref<GLTFMesh> mesh = state->meshes[gltf_node->mesh]; + ERR_CONTINUE(gltf_node->mesh < 0 || gltf_node->mesh >= p_state->meshes.size()); + Ref<GLTFMesh> mesh = p_state->meshes[gltf_node->mesh]; ERR_CONTINUE(mesh.is_null()); ERR_CONTINUE(mesh->get_mesh().is_null()); ERR_CONTINUE(mesh->get_mesh()->get_mesh().is_null()); @@ -6025,45 +6078,45 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap, } } else { // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies. - const double increment = 1.0 / bake_fps; + const double increment = 1.0 / p_bake_fps; double time = 0.0; bool last = false; while (true) { real_t blend = _interpolate_track<real_t>(track.weight_tracks[i].times, track.weight_tracks[i].values, time, gltf_interp); - animation->blend_shape_track_insert_key(track_idx, time, blend); + animation->blend_shape_track_insert_key(track_idx, time - anim_start, blend); if (last) { break; } time += increment; - if (time >= length) { + if (time >= anim_end) { last = true; - time = length; + time = anim_end; } } } } } - animation->set_length(length); + animation->set_length(anim_end - anim_start); Ref<AnimationLibrary> library; - if (!ap->has_animation_library("")) { + if (!p_animation_player->has_animation_library("")) { library.instantiate(); - ap->add_animation_library("", library); + p_animation_player->add_animation_library("", library); } else { - library = ap->get_animation_library(""); + library = p_animation_player->get_animation_library(""); } library->add_animation(anim_name, animation); } -void GLTFDocument::_convert_mesh_instances(Ref<GLTFState> state) { - for (GLTFNodeIndex mi_node_i = 0; mi_node_i < state->nodes.size(); ++mi_node_i) { - Ref<GLTFNode> node = state->nodes[mi_node_i]; +void GLTFDocument::_convert_mesh_instances(Ref<GLTFState> p_state) { + for (GLTFNodeIndex mi_node_i = 0; mi_node_i < p_state->nodes.size(); ++mi_node_i) { + Ref<GLTFNode> node = p_state->nodes[mi_node_i]; if (node->mesh < 0) { continue; } - HashMap<GLTFNodeIndex, Node *>::Iterator mi_element = state->scene_nodes.find(mi_node_i); + HashMap<GLTFNodeIndex, Node *>::Iterator mi_element = p_state->scene_nodes.find(mi_node_i); if (!mi_element) { continue; } @@ -6094,10 +6147,10 @@ void GLTFDocument::_convert_mesh_instances(Ref<GLTFState> state) { if (skel_node != nullptr) { godot_skeleton = cast_to<Skeleton3D>(skel_node); } - if (godot_skeleton != nullptr && state->skeleton3d_to_gltf_skeleton.has(godot_skeleton->get_instance_id())) { + if (godot_skeleton != nullptr && p_state->skeleton3d_to_gltf_skeleton.has(godot_skeleton->get_instance_id())) { // This is a skinned mesh. If the mesh has no ARRAY_WEIGHTS or ARRAY_BONES, it will be invisible. - const GLTFSkeletonIndex skeleton_gltf_i = state->skeleton3d_to_gltf_skeleton[godot_skeleton->get_instance_id()]; - Ref<GLTFSkeleton> gltf_skeleton = state->skeletons[skeleton_gltf_i]; + const GLTFSkeletonIndex skeleton_gltf_i = p_state->skeleton3d_to_gltf_skeleton[godot_skeleton->get_instance_id()]; + Ref<GLTFSkeleton> gltf_skeleton = p_state->skeletons[skeleton_gltf_i]; int bone_cnt = skeleton->get_bone_count(); ERR_FAIL_COND(bone_cnt != gltf_skeleton->joints.size()); @@ -6111,8 +6164,8 @@ void GLTFDocument::_convert_mesh_instances(Ref<GLTFState> state) { if (!gltf_skeleton->roots.is_empty()) { root_gltf_i = gltf_skeleton->roots[0]; } - if (state->skin_and_skeleton3d_to_gltf_skin.has(gltf_skin_key) && state->skin_and_skeleton3d_to_gltf_skin[gltf_skin_key].has(gltf_skel_key)) { - skin_gltf_i = state->skin_and_skeleton3d_to_gltf_skin[gltf_skin_key][gltf_skel_key]; + if (p_state->skin_and_skeleton3d_to_gltf_skin.has(gltf_skin_key) && p_state->skin_and_skeleton3d_to_gltf_skin[gltf_skin_key].has(gltf_skel_key)) { + skin_gltf_i = p_state->skin_and_skeleton3d_to_gltf_skin[gltf_skin_key][gltf_skel_key]; } else { if (skin.is_null()) { // Note that gltf_skin_key should remain null, so these can share a reference. @@ -6149,9 +6202,9 @@ void GLTFDocument::_convert_mesh_instances(Ref<GLTFState> state) { gltf_skin->joint_i_to_bone_i[bind_i] = bone_i; gltf_skin->joint_i_to_name[bind_i] = bind_name; } - skin_gltf_i = state->skins.size(); - state->skins.push_back(gltf_skin); - state->skin_and_skeleton3d_to_gltf_skin[gltf_skin_key][gltf_skel_key] = skin_gltf_i; + skin_gltf_i = p_state->skins.size(); + p_state->skins.push_back(gltf_skin); + p_state->skin_and_skeleton3d_to_gltf_skin[gltf_skin_key][gltf_skel_key] = skin_gltf_i; } node->skin = skin_gltf_i; node->skeleton = skeleton_gltf_i; @@ -6159,14 +6212,14 @@ void GLTFDocument::_convert_mesh_instances(Ref<GLTFState> state) { } } -float GLTFDocument::solve_metallic(float p_dielectric_specular, float diffuse, float specular, float p_one_minus_specular_strength) { - if (specular <= p_dielectric_specular) { +float GLTFDocument::solve_metallic(float p_dielectric_specular, float p_diffuse, float p_specular, float p_one_minus_specular_strength) { + if (p_specular <= p_dielectric_specular) { return 0.0f; } const float a = p_dielectric_specular; - const float b = diffuse * p_one_minus_specular_strength / (1.0f - p_dielectric_specular) + specular - 2.0f * p_dielectric_specular; - const float c = p_dielectric_specular - specular; + const float b = p_diffuse * p_one_minus_specular_strength / (1.0f - p_dielectric_specular) + p_specular - 2.0f * p_dielectric_specular; + const float c = p_dielectric_specular - p_specular; const float D = b * b - 4.0f * a * c; return CLAMP((-b + Math::sqrt(D)) / (2.0f * a), 0.0f, 1.0f); } @@ -6190,21 +6243,21 @@ float GLTFDocument::get_max_component(const Color &p_color) { return MAX(MAX(r, g), b); } -void GLTFDocument::_process_mesh_instances(Ref<GLTFState> state, Node *scene_root) { - for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); ++node_i) { - Ref<GLTFNode> node = state->nodes[node_i]; +void GLTFDocument::_process_mesh_instances(Ref<GLTFState> p_state, Node *p_scene_root) { + for (GLTFNodeIndex node_i = 0; node_i < p_state->nodes.size(); ++node_i) { + Ref<GLTFNode> node = p_state->nodes[node_i]; if (node->skin >= 0 && node->mesh >= 0) { const GLTFSkinIndex skin_i = node->skin; - HashMap<GLTFNodeIndex, Node *>::Iterator mi_element = state->scene_nodes.find(node_i); + HashMap<GLTFNodeIndex, Node *>::Iterator mi_element = p_state->scene_nodes.find(node_i); ERR_CONTINUE_MSG(!mi_element, vformat("Unable to find node %d", node_i)); ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(mi_element->value); ERR_CONTINUE_MSG(mi == nullptr, vformat("Unable to cast node %d of type %s to ImporterMeshInstance3D", node_i, mi_element->value->get_class_name())); - const GLTFSkeletonIndex skel_i = state->skins.write[node->skin]->skeleton; - Ref<GLTFSkeleton> gltf_skeleton = state->skeletons.write[skel_i]; + const GLTFSkeletonIndex skel_i = p_state->skins.write[node->skin]->skeleton; + Ref<GLTFSkeleton> gltf_skeleton = p_state->skeletons.write[skel_i]; Skeleton3D *skeleton = gltf_skeleton->godot_skeleton; ERR_CONTINUE_MSG(skeleton == nullptr, vformat("Unable to find Skeleton for node %d skin %d", node_i, skin_i)); @@ -6212,14 +6265,14 @@ void GLTFDocument::_process_mesh_instances(Ref<GLTFState> state, Node *scene_roo skeleton->add_child(mi, true); mi->set_owner(skeleton->get_owner()); - mi->set_skin(state->skins.write[skin_i]->godot_skin); + mi->set_skin(p_state->skins.write[skin_i]->godot_skin); mi->set_skeleton_path(mi->get_path_to(skeleton)); mi->set_transform(Transform3D()); } } } -GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state, GLTFAnimation::Track p_track, Ref<Animation> p_animation, int32_t p_track_i, GLTFNodeIndex p_node_i) { +GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_state, GLTFAnimation::Track p_track, Ref<Animation> p_animation, int32_t p_track_i, GLTFNodeIndex p_node_i) { Animation::InterpolationType interpolation = p_animation->track_get_interpolation_type(p_track_i); GLTFAnimation::Interpolation gltf_interpolation = GLTFAnimation::INTERP_LINEAR; @@ -6365,11 +6418,11 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state return p_track; } -void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, String p_animation_track_name) { - Ref<Animation> animation = ap->get_animation(p_animation_track_name); +void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, String p_animation_track_name) { + Ref<Animation> animation = p_animation_player->get_animation(p_animation_track_name); Ref<GLTFAnimation> gltf_animation; gltf_animation.instantiate(); - gltf_animation->set_name(_gen_unique_name(state, p_animation_track_name)); + gltf_animation->set_name(_gen_unique_name(p_state, p_animation_track_name)); for (int32_t track_i = 0; track_i < animation->get_track_count(); track_i++) { if (!animation->track_is_enabled(track_i)) { @@ -6379,8 +6432,8 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, if (String(orig_track_path).contains(":position")) { const Vector<String> node_suffix = String(orig_track_path).split(":position"); const NodePath path = node_suffix[0]; - const Node *node = ap->get_parent()->get_node_or_null(path); - for (const KeyValue<GLTFNodeIndex, Node *> &position_scene_node_i : state->scene_nodes) { + const Node *node = p_animation_player->get_parent()->get_node_or_null(path); + for (const KeyValue<GLTFNodeIndex, Node *> &position_scene_node_i : p_state->scene_nodes) { if (position_scene_node_i.value == node) { GLTFNodeIndex node_index = position_scene_node_i.key; HashMap<int, GLTFAnimation::Track>::Iterator position_track_i = gltf_animation->get_tracks().find(node_index); @@ -6388,15 +6441,15 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, if (position_track_i) { track = position_track_i->value; } - track = _convert_animation_track(state, track, animation, track_i, node_index); + track = _convert_animation_track(p_state, track, animation, track_i, node_index); gltf_animation->get_tracks().insert(node_index, track); } } } else if (String(orig_track_path).contains(":rotation_degrees")) { const Vector<String> node_suffix = String(orig_track_path).split(":rotation_degrees"); const NodePath path = node_suffix[0]; - const Node *node = ap->get_parent()->get_node_or_null(path); - for (const KeyValue<GLTFNodeIndex, Node *> &rotation_degree_scene_node_i : state->scene_nodes) { + const Node *node = p_animation_player->get_parent()->get_node_or_null(path); + for (const KeyValue<GLTFNodeIndex, Node *> &rotation_degree_scene_node_i : p_state->scene_nodes) { if (rotation_degree_scene_node_i.value == node) { GLTFNodeIndex node_index = rotation_degree_scene_node_i.key; HashMap<int, GLTFAnimation::Track>::Iterator rotation_degree_track_i = gltf_animation->get_tracks().find(node_index); @@ -6404,15 +6457,15 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, if (rotation_degree_track_i) { track = rotation_degree_track_i->value; } - track = _convert_animation_track(state, track, animation, track_i, node_index); + track = _convert_animation_track(p_state, track, animation, track_i, node_index); gltf_animation->get_tracks().insert(node_index, track); } } } else if (String(orig_track_path).contains(":scale")) { const Vector<String> node_suffix = String(orig_track_path).split(":scale"); const NodePath path = node_suffix[0]; - const Node *node = ap->get_parent()->get_node_or_null(path); - for (const KeyValue<GLTFNodeIndex, Node *> &scale_scene_node_i : state->scene_nodes) { + const Node *node = p_animation_player->get_parent()->get_node_or_null(path); + for (const KeyValue<GLTFNodeIndex, Node *> &scale_scene_node_i : p_state->scene_nodes) { if (scale_scene_node_i.value == node) { GLTFNodeIndex node_index = scale_scene_node_i.key; HashMap<int, GLTFAnimation::Track>::Iterator scale_track_i = gltf_animation->get_tracks().find(node_index); @@ -6420,18 +6473,18 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, if (scale_track_i) { track = scale_track_i->value; } - track = _convert_animation_track(state, track, animation, track_i, node_index); + track = _convert_animation_track(p_state, track, animation, track_i, node_index); gltf_animation->get_tracks().insert(node_index, track); } } } else if (String(orig_track_path).contains(":transform")) { const Vector<String> node_suffix = String(orig_track_path).split(":transform"); const NodePath path = node_suffix[0]; - const Node *node = ap->get_parent()->get_node_or_null(path); - for (const KeyValue<GLTFNodeIndex, Node *> &transform_track_i : state->scene_nodes) { + const Node *node = p_animation_player->get_parent()->get_node_or_null(path); + for (const KeyValue<GLTFNodeIndex, Node *> &transform_track_i : p_state->scene_nodes) { if (transform_track_i.value == node) { GLTFAnimation::Track track; - track = _convert_animation_track(state, track, animation, track_i, transform_track_i.key); + track = _convert_animation_track(p_state, track, animation, track_i, transform_track_i.key); gltf_animation->get_tracks().insert(transform_track_i.key, track); } } @@ -6439,12 +6492,12 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, const Vector<String> node_suffix = String(orig_track_path).split(":"); const NodePath path = node_suffix[0]; const String suffix = node_suffix[1]; - Node *node = ap->get_parent()->get_node_or_null(path); + Node *node = p_animation_player->get_parent()->get_node_or_null(path); MeshInstance3D *mi = cast_to<MeshInstance3D>(node); Ref<Mesh> mesh = mi->get_mesh(); ERR_CONTINUE(mesh.is_null()); int32_t mesh_index = -1; - for (const KeyValue<GLTFNodeIndex, Node *> &mesh_track_i : state->scene_nodes) { + for (const KeyValue<GLTFNodeIndex, Node *> &mesh_track_i : p_state->scene_nodes) { if (mesh_track_i.value == node) { mesh_index = mesh_track_i.key; } @@ -6497,15 +6550,15 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, const String node = node_suffix[0]; const NodePath node_path = node; const String suffix = node_suffix[1]; - Node *godot_node = ap->get_parent()->get_node_or_null(node_path); + Node *godot_node = p_animation_player->get_parent()->get_node_or_null(node_path); Skeleton3D *skeleton = nullptr; GLTFSkeletonIndex skeleton_gltf_i = -1; - for (GLTFSkeletonIndex skeleton_i = 0; skeleton_i < state->skeletons.size(); skeleton_i++) { - if (state->skeletons[skeleton_i]->godot_skeleton == cast_to<Skeleton3D>(godot_node)) { - skeleton = state->skeletons[skeleton_i]->godot_skeleton; + for (GLTFSkeletonIndex skeleton_i = 0; skeleton_i < p_state->skeletons.size(); skeleton_i++) { + if (p_state->skeletons[skeleton_i]->godot_skeleton == cast_to<Skeleton3D>(godot_node)) { + skeleton = p_state->skeletons[skeleton_i]->godot_skeleton; skeleton_gltf_i = skeleton_i; ERR_CONTINUE(!skeleton); - Ref<GLTFSkeleton> skeleton_gltf = state->skeletons[skeleton_gltf_i]; + Ref<GLTFSkeleton> skeleton_gltf = p_state->skeletons[skeleton_gltf_i]; int32_t bone = skeleton->find_bone(suffix); ERR_CONTINUE(bone == -1); if (!skeleton_gltf->godot_bone_node.has(bone)) { @@ -6517,14 +6570,14 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, if (property_track_i) { track = property_track_i->value; } - track = _convert_animation_track(state, track, animation, track_i, node_i); + track = _convert_animation_track(p_state, track, animation, track_i, node_i); gltf_animation->get_tracks()[node_i] = track; } } } else if (!String(orig_track_path).contains(":")) { - ERR_CONTINUE(!ap->get_parent()); - Node *godot_node = ap->get_parent()->get_node_or_null(orig_track_path); - for (const KeyValue<GLTFNodeIndex, Node *> &scene_node_i : state->scene_nodes) { + ERR_CONTINUE(!p_animation_player->get_parent()); + Node *godot_node = p_animation_player->get_parent()->get_node_or_null(orig_track_path); + for (const KeyValue<GLTFNodeIndex, Node *> &scene_node_i : p_state->scene_nodes) { if (scene_node_i.value == godot_node) { GLTFNodeIndex node_i = scene_node_i.key; HashMap<int, GLTFAnimation::Track>::Iterator node_track_i = gltf_animation->get_tracks().find(node_i); @@ -6532,7 +6585,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, if (node_track_i) { track = node_track_i->value; } - track = _convert_animation_track(state, track, animation, track_i, node_i); + track = _convert_animation_track(p_state, track, animation, track_i, node_i); gltf_animation->get_tracks()[node_i] = track; break; } @@ -6540,42 +6593,42 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, } } if (gltf_animation->get_tracks().size()) { - state->animations.push_back(gltf_animation); + p_state->animations.push_back(gltf_animation); } } -Error GLTFDocument::_parse(Ref<GLTFState> state, String p_path, Ref<FileAccess> f, int p_bake_fps) { +Error GLTFDocument::_parse(Ref<GLTFState> p_state, String p_path, Ref<FileAccess> p_file) { Error err; - if (f.is_null()) { + if (p_file.is_null()) { return FAILED; } - f->seek(0); - uint32_t magic = f->get_32(); + p_file->seek(0); + uint32_t magic = p_file->get_32(); if (magic == 0x46546C67) { //binary file //text file - f->seek(0); - err = _parse_glb(f, state); + p_file->seek(0); + err = _parse_glb(p_file, p_state); if (err != OK) { return err; } } else { - f->seek(0); - String text = f->get_as_utf8_string(); + p_file->seek(0); + String text = p_file->get_as_utf8_string(); JSON json; err = json.parse(text); if (err != OK) { _err_print_error("", "", json.get_error_line(), json.get_error_message().utf8().get_data(), false, ERR_HANDLER_SCRIPT); } ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); - state->json = json.get_data(); + p_state->json = json.get_data(); } - if (!state->json.has("asset")) { + if (!p_state->json.has("asset")) { return ERR_PARSE_ERROR; } - Dictionary asset = state->json["asset"]; + Dictionary asset = p_state->json["asset"]; if (!asset.has("version")) { return ERR_PARSE_ERROR; @@ -6583,17 +6636,19 @@ Error GLTFDocument::_parse(Ref<GLTFState> state, String p_path, Ref<FileAccess> String version = asset["version"]; - state->major_version = version.get_slice(".", 0).to_int(); - state->minor_version = version.get_slice(".", 1).to_int(); + p_state->major_version = version.get_slice(".", 0).to_int(); + p_state->minor_version = version.get_slice(".", 1).to_int(); - for (int32_t ext_i = 0; ext_i < document_extensions.size(); ext_i++) { - Ref<GLTFDocumentExtension> ext = document_extensions[ext_i]; + document_extensions.clear(); + for (Ref<GLTFDocumentExtension> ext : all_document_extensions) { ERR_CONTINUE(ext.is_null()); - err = ext->import_preflight(state); - ERR_FAIL_COND_V(err != OK, err); + err = ext->import_preflight(p_state, p_state->json["extensionsUsed"]); + if (err == OK) { + document_extensions.push_back(ext); + } } - err = _parse_gltf_state(state, p_path, p_bake_fps); + err = _parse_gltf_state(p_state, p_path); ERR_FAIL_COND_V(err != OK, err); return OK; @@ -6626,47 +6681,43 @@ Dictionary _serialize_texture_transform_uv(Vector2 p_offset, Vector2 p_scale) { } Dictionary GLTFDocument::_serialize_texture_transform_uv1(Ref<BaseMaterial3D> p_material) { - if (p_material.is_valid()) { - Vector3 offset = p_material->get_uv1_offset(); - Vector3 scale = p_material->get_uv1_scale(); - return _serialize_texture_transform_uv(Vector2(offset.x, offset.y), Vector2(scale.x, scale.y)); - } - return Dictionary(); + ERR_FAIL_NULL_V(p_material, Dictionary()); + Vector3 offset = p_material->get_uv1_offset(); + Vector3 scale = p_material->get_uv1_scale(); + return _serialize_texture_transform_uv(Vector2(offset.x, offset.y), Vector2(scale.x, scale.y)); } Dictionary GLTFDocument::_serialize_texture_transform_uv2(Ref<BaseMaterial3D> p_material) { - if (p_material.is_valid()) { - Vector3 offset = p_material->get_uv2_offset(); - Vector3 scale = p_material->get_uv2_scale(); - return _serialize_texture_transform_uv(Vector2(offset.x, offset.y), Vector2(scale.x, scale.y)); - } - return Dictionary(); + ERR_FAIL_NULL_V(p_material, Dictionary()); + Vector3 offset = p_material->get_uv2_offset(); + Vector3 scale = p_material->get_uv2_scale(); + return _serialize_texture_transform_uv(Vector2(offset.x, offset.y), Vector2(scale.x, scale.y)); } -Error GLTFDocument::_serialize_version(Ref<GLTFState> state) { +Error GLTFDocument::_serialize_version(Ref<GLTFState> p_state) { const String version = "2.0"; - state->major_version = version.get_slice(".", 0).to_int(); - state->minor_version = version.get_slice(".", 1).to_int(); + p_state->major_version = version.get_slice(".", 0).to_int(); + p_state->minor_version = version.get_slice(".", 1).to_int(); Dictionary asset; asset["version"] = version; String hash = String(VERSION_HASH); asset["generator"] = String(VERSION_FULL_NAME) + String("@") + (hash.is_empty() ? String("unknown") : hash); - state->json["asset"] = asset; + p_state->json["asset"] = asset; ERR_FAIL_COND_V(!asset.has("version"), Error::FAILED); - ERR_FAIL_COND_V(!state->json.has("asset"), Error::FAILED); + ERR_FAIL_COND_V(!p_state->json.has("asset"), Error::FAILED); return OK; } -Error GLTFDocument::_serialize_file(Ref<GLTFState> state, const String p_path) { +Error GLTFDocument::_serialize_file(Ref<GLTFState> p_state, const String p_path) { Error err = FAILED; if (p_path.to_lower().ends_with("glb")) { - err = _encode_buffer_glb(state, p_path); + err = _encode_buffer_glb(p_state, p_path); ERR_FAIL_COND_V(err != OK, err); - Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE, &err); - ERR_FAIL_COND_V(f.is_null(), FAILED); + Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err); + ERR_FAIL_COND_V(file.is_null(), FAILED); - String json = Variant(state->json).to_json_string(); + String json = Variant(p_state->json).to_json_string(); const uint32_t magic = 0x46546C67; // GLTF const int32_t header_size = 12; @@ -6677,106 +6728,104 @@ Error GLTFDocument::_serialize_file(Ref<GLTFState> state, const String p_path) { const uint32_t text_chunk_type = 0x4E4F534A; //JSON uint32_t binary_data_length = 0; - if (state->buffers.size()) { - binary_data_length = state->buffers[0].size(); + if (p_state->buffers.size()) { + binary_data_length = p_state->buffers[0].size(); } const uint32_t binary_chunk_length = ((binary_data_length + 3) & (~3)); const uint32_t binary_chunk_type = 0x004E4942; //BIN - f->create(FileAccess::ACCESS_RESOURCES); - f->store_32(magic); - f->store_32(state->major_version); // version - f->store_32(header_size + chunk_header_size + text_chunk_length + chunk_header_size + binary_chunk_length); // length - f->store_32(text_chunk_length); - f->store_32(text_chunk_type); - f->store_buffer((uint8_t *)&cs[0], cs.length()); + file->create(FileAccess::ACCESS_RESOURCES); + file->store_32(magic); + file->store_32(p_state->major_version); // version + file->store_32(header_size + chunk_header_size + text_chunk_length + chunk_header_size + binary_chunk_length); // length + file->store_32(text_chunk_length); + file->store_32(text_chunk_type); + file->store_buffer((uint8_t *)&cs[0], cs.length()); for (uint32_t pad_i = text_data_length; pad_i < text_chunk_length; pad_i++) { - f->store_8(' '); + file->store_8(' '); } if (binary_chunk_length) { - f->store_32(binary_chunk_length); - f->store_32(binary_chunk_type); - f->store_buffer(state->buffers[0].ptr(), binary_data_length); + file->store_32(binary_chunk_length); + file->store_32(binary_chunk_type); + file->store_buffer(p_state->buffers[0].ptr(), binary_data_length); } for (uint32_t pad_i = binary_data_length; pad_i < binary_chunk_length; pad_i++) { - f->store_8(0); + file->store_8(0); } } else { - err = _encode_buffer_bins(state, p_path); + err = _encode_buffer_bins(p_state, p_path); ERR_FAIL_COND_V(err != OK, err); - Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::WRITE, &err); - ERR_FAIL_COND_V(f.is_null(), FAILED); + Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err); + ERR_FAIL_COND_V(file.is_null(), FAILED); - f->create(FileAccess::ACCESS_RESOURCES); - String json = Variant(state->json).to_json_string(); - f->store_string(json); + file->create(FileAccess::ACCESS_RESOURCES); + String json = Variant(p_state->json).to_json_string(); + file->store_string(json); } return err; } void GLTFDocument::_bind_methods() { - ClassDB::bind_method(D_METHOD("append_from_file", "path", "state", "flags", "bake_fps", "base_path"), - &GLTFDocument::append_from_file, DEFVAL(0), DEFVAL(30), DEFVAL(String())); - ClassDB::bind_method(D_METHOD("append_from_buffer", "bytes", "base_path", "state", "flags", "bake_fps"), - &GLTFDocument::append_from_buffer, DEFVAL(0), DEFVAL(30)); - ClassDB::bind_method(D_METHOD("append_from_scene", "node", "state", "flags", "bake_fps"), - &GLTFDocument::append_from_scene, DEFVAL(0), DEFVAL(30)); - ClassDB::bind_method(D_METHOD("generate_scene", "state", "bake_fps"), - &GLTFDocument::generate_scene, DEFVAL(30)); + ClassDB::bind_method(D_METHOD("append_from_file", "path", "state", "flags", "base_path"), + &GLTFDocument::append_from_file, DEFVAL(0), DEFVAL(String())); + ClassDB::bind_method(D_METHOD("append_from_buffer", "bytes", "base_path", "state", "flags"), + &GLTFDocument::append_from_buffer, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("append_from_scene", "node", "state", "flags"), + &GLTFDocument::append_from_scene, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("generate_scene", "state", "bake_fps", "trimming"), + &GLTFDocument::generate_scene, DEFVAL(30), DEFVAL(false)); ClassDB::bind_method(D_METHOD("generate_buffer", "state"), &GLTFDocument::generate_buffer); ClassDB::bind_method(D_METHOD("write_to_filesystem", "state", "path"), &GLTFDocument::write_to_filesystem); - ClassDB::bind_method(D_METHOD("set_extensions", "extensions"), - &GLTFDocument::set_extensions); - ClassDB::bind_method(D_METHOD("get_extensions"), - &GLTFDocument::get_extensions); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "extensions", PROPERTY_HINT_ARRAY_TYPE, - vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "GLTFDocumentExtension"), - PROPERTY_USAGE_DEFAULT), - "set_extensions", "get_extensions"); + ClassDB::bind_static_method("GLTFDocument", D_METHOD("register_gltf_document_extension", "extension", "first_priority"), + &GLTFDocument::register_gltf_document_extension, DEFVAL(false)); + ClassDB::bind_static_method("GLTFDocument", D_METHOD("unregister_gltf_document_extension", "extension"), + &GLTFDocument::unregister_gltf_document_extension); } -void GLTFDocument::_build_parent_hierachy(Ref<GLTFState> state) { +void GLTFDocument::_build_parent_hierachy(Ref<GLTFState> p_state) { // build the hierarchy - for (GLTFNodeIndex node_i = 0; node_i < state->nodes.size(); node_i++) { - for (int j = 0; j < state->nodes[node_i]->children.size(); j++) { - GLTFNodeIndex child_i = state->nodes[node_i]->children[j]; - ERR_FAIL_INDEX(child_i, state->nodes.size()); - if (state->nodes.write[child_i]->parent != -1) { + for (GLTFNodeIndex node_i = 0; node_i < p_state->nodes.size(); node_i++) { + for (int j = 0; j < p_state->nodes[node_i]->children.size(); j++) { + GLTFNodeIndex child_i = p_state->nodes[node_i]->children[j]; + ERR_FAIL_INDEX(child_i, p_state->nodes.size()); + if (p_state->nodes.write[child_i]->parent != -1) { continue; } - state->nodes.write[child_i]->parent = node_i; + p_state->nodes.write[child_i]->parent = node_i; } } } -void GLTFDocument::set_extensions(TypedArray<GLTFDocumentExtension> p_extensions) { - document_extensions = p_extensions; +Vector<Ref<GLTFDocumentExtension>> GLTFDocument::all_document_extensions; + +void GLTFDocument::register_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension, bool p_first_priority) { + if (all_document_extensions.find(p_extension) == -1) { + if (p_first_priority) { + all_document_extensions.insert(0, p_extension); + } else { + all_document_extensions.push_back(p_extension); + } + } } -TypedArray<GLTFDocumentExtension> GLTFDocument::get_extensions() const { - return document_extensions; +void GLTFDocument::unregister_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension) { + all_document_extensions.erase(p_extension); } -GLTFDocument::GLTFDocument() { - bool is_editor = ::Engine::get_singleton()->is_editor_hint(); - if (is_editor) { - return; - } - Ref<GLTFDocumentExtensionConvertImporterMesh> extension_editor; - extension_editor.instantiate(); - document_extensions.push_back(extension_editor); +void GLTFDocument::unregister_all_gltf_document_extensions() { + all_document_extensions.clear(); } -PackedByteArray GLTFDocument::_serialize_glb_buffer(Ref<GLTFState> state, Error *r_err) { - Error err = _encode_buffer_glb(state, ""); +PackedByteArray GLTFDocument::_serialize_glb_buffer(Ref<GLTFState> p_state, Error *r_err) { + Error err = _encode_buffer_glb(p_state, ""); if (r_err) { *r_err = err; } ERR_FAIL_COND_V(err != OK, PackedByteArray()); - String json = Variant(state->json).to_json_string(); + String json = Variant(p_state->json).to_json_string(); const uint32_t magic = 0x46546C67; // GLTF const int32_t header_size = 12; @@ -6790,8 +6839,8 @@ PackedByteArray GLTFDocument::_serialize_glb_buffer(Ref<GLTFState> state, Error const uint32_t text_chunk_type = 0x4E4F534A; //JSON int32_t binary_data_length = 0; - if (state->buffers.size()) { - binary_data_length = state->buffers[0].size(); + if (p_state->buffers.size()) { + binary_data_length = p_state->buffers[0].size(); } const int32_t binary_chunk_length = binary_data_length; const int32_t binary_chunk_type = 0x004E4942; //BIN @@ -6799,7 +6848,7 @@ PackedByteArray GLTFDocument::_serialize_glb_buffer(Ref<GLTFState> state, Error Ref<StreamPeerBuffer> buffer; buffer.instantiate(); buffer->put_32(magic); - buffer->put_32(state->major_version); // version + buffer->put_32(p_state->major_version); // version buffer->put_32(header_size + chunk_header_size + text_chunk_length + chunk_header_size + binary_data_length); // length buffer->put_32(text_chunk_length); buffer->put_32(text_chunk_type); @@ -6807,211 +6856,210 @@ PackedByteArray GLTFDocument::_serialize_glb_buffer(Ref<GLTFState> state, Error if (binary_chunk_length) { buffer->put_32(binary_chunk_length); buffer->put_32(binary_chunk_type); - buffer->put_data(state->buffers[0].ptr(), binary_data_length); + buffer->put_data(p_state->buffers[0].ptr(), binary_data_length); } return buffer->get_data_array(); } -PackedByteArray GLTFDocument::generate_buffer(Ref<GLTFState> state) { - ERR_FAIL_NULL_V(state, PackedByteArray()); - Error err = _serialize(state, ""); +PackedByteArray GLTFDocument::generate_buffer(Ref<GLTFState> p_state) { + ERR_FAIL_NULL_V(p_state, PackedByteArray()); + Error err = _serialize(p_state, ""); ERR_FAIL_COND_V(err != OK, PackedByteArray()); - PackedByteArray bytes = _serialize_glb_buffer(state, &err); + PackedByteArray bytes = _serialize_glb_buffer(p_state, &err); return bytes; } -Error GLTFDocument::write_to_filesystem(Ref<GLTFState> state, const String &p_path) { - ERR_FAIL_NULL_V(state, ERR_INVALID_PARAMETER); - Error err = _serialize(state, p_path); +Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_path) { + ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + Error err = _serialize(p_state, p_path); if (err != OK) { return err; } - err = _serialize_file(state, p_path); + err = _serialize_file(p_state, p_path); if (err != OK) { return Error::FAILED; } return OK; } -Node *GLTFDocument::generate_scene(Ref<GLTFState> state, int32_t p_bake_fps) { - ERR_FAIL_NULL_V(state, nullptr); - ERR_FAIL_INDEX_V(0, state->root_nodes.size(), nullptr); +Node *GLTFDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool p_trimming) { + ERR_FAIL_NULL_V(p_state, nullptr); + ERR_FAIL_INDEX_V(0, p_state->root_nodes.size(), nullptr); Error err = OK; - GLTFNodeIndex gltf_root = state->root_nodes.write[0]; - Node *gltf_root_node = state->get_scene_node(gltf_root); + GLTFNodeIndex gltf_root = p_state->root_nodes.write[0]; + Node *gltf_root_node = p_state->get_scene_node(gltf_root); Node *root = gltf_root_node->get_parent(); ERR_FAIL_NULL_V(root, nullptr); - _process_mesh_instances(state, root); - if (state->get_create_animations() && state->animations.size()) { + _process_mesh_instances(p_state, root); + if (p_state->get_create_animations() && p_state->animations.size()) { AnimationPlayer *ap = memnew(AnimationPlayer); root->add_child(ap, true); ap->set_owner(root); - for (int i = 0; i < state->animations.size(); i++) { - _import_animation(state, ap, i, p_bake_fps); + for (int i = 0; i < p_state->animations.size(); i++) { + _import_animation(p_state, ap, i, p_bake_fps, p_trimming); } } - for (KeyValue<GLTFNodeIndex, Node *> E : state->scene_nodes) { + for (KeyValue<GLTFNodeIndex, Node *> E : p_state->scene_nodes) { ERR_CONTINUE(!E.value); - for (int32_t ext_i = 0; ext_i < document_extensions.size(); ext_i++) { - Ref<GLTFDocumentExtension> ext = document_extensions[ext_i]; + for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); - ERR_CONTINUE(!state->json.has("nodes")); - Array nodes = state->json["nodes"]; + ERR_CONTINUE(!p_state->json.has("nodes")); + Array nodes = p_state->json["nodes"]; ERR_CONTINUE(E.key >= nodes.size()); ERR_CONTINUE(E.key < 0); Dictionary node_json = nodes[E.key]; - Ref<GLTFNode> gltf_node = state->nodes[E.key]; - err = ext->import_node(state, gltf_node, node_json, E.value); + Ref<GLTFNode> gltf_node = p_state->nodes[E.key]; + err = ext->import_node(p_state, gltf_node, node_json, E.value); ERR_CONTINUE(err != OK); } } - for (int32_t ext_i = 0; ext_i < document_extensions.size(); ext_i++) { - Ref<GLTFDocumentExtension> ext = document_extensions[ext_i]; + for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); - err = ext->import_post(state, root); + err = ext->import_post(p_state, root); ERR_CONTINUE(err != OK); } ERR_FAIL_NULL_V(root, nullptr); return root; } -Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> state, uint32_t p_flags, int32_t p_bake_fps) { - ERR_FAIL_COND_V(state.is_null(), FAILED); - state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS; - state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS; +Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint32_t p_flags) { + ERR_FAIL_COND_V(p_state.is_null(), FAILED); + p_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS; + p_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS; - for (int32_t ext_i = 0; ext_i < document_extensions.size(); ext_i++) { - Ref<GLTFDocumentExtension> ext = document_extensions[ext_i]; + document_extensions.clear(); + for (Ref<GLTFDocumentExtension> ext : all_document_extensions) { ERR_CONTINUE(ext.is_null()); Error err = ext->export_preflight(p_node); - ERR_FAIL_COND_V(err != OK, FAILED); + if (err == OK) { + document_extensions.push_back(ext); + } } - _convert_scene_node(state, p_node, -1, -1); - if (!state->buffers.size()) { - state->buffers.push_back(Vector<uint8_t>()); + _convert_scene_node(p_state, p_node, -1, -1); + if (!p_state->buffers.size()) { + p_state->buffers.push_back(Vector<uint8_t>()); } return OK; } -Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> state, uint32_t p_flags, int32_t p_bake_fps) { - ERR_FAIL_COND_V(state.is_null(), FAILED); +Error GLTFDocument::append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> p_state, uint32_t p_flags) { + ERR_FAIL_COND_V(p_state.is_null(), FAILED); // TODO Add missing texture and missing .bin file paths to r_missing_deps 2021-09-10 fire Error err = FAILED; - state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS; - state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS; + p_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS; + p_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS; Ref<FileAccessMemory> file_access; file_access.instantiate(); file_access->open_custom(p_bytes.ptr(), p_bytes.size()); - state->base_path = p_base_path.get_base_dir(); - err = _parse(state, state->base_path, file_access, p_bake_fps); + p_state->base_path = p_base_path.get_base_dir(); + err = _parse(p_state, p_state->base_path, file_access); ERR_FAIL_COND_V(err != OK, err); - for (int32_t ext_i = 0; ext_i < document_extensions.size(); ext_i++) { - Ref<GLTFDocumentExtension> ext = document_extensions[ext_i]; + for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); - err = ext->import_post_parse(state); + err = ext->import_post_parse(p_state); ERR_FAIL_COND_V(err != OK, err); } return OK; } -Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> state, const String &p_search_path, float p_bake_fps) { +Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_search_path) { Error err; /* PARSE EXTENSIONS */ - err = _parse_gltf_extensions(state); + err = _parse_gltf_extensions(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* PARSE SCENE */ - err = _parse_scenes(state); + err = _parse_scenes(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* PARSE NODES */ - err = _parse_nodes(state); + err = _parse_nodes(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* PARSE BUFFERS */ - err = _parse_buffers(state, p_search_path); + err = _parse_buffers(p_state, p_search_path); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* PARSE BUFFER VIEWS */ - err = _parse_buffer_views(state); + err = _parse_buffer_views(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* PARSE ACCESSORS */ - err = _parse_accessors(state); + err = _parse_accessors(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); - if (!state->discard_meshes_and_materials) { + if (!p_state->discard_meshes_and_materials) { /* PARSE IMAGES */ - err = _parse_images(state, p_search_path); + err = _parse_images(p_state, p_search_path); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* PARSE TEXTURE SAMPLERS */ - err = _parse_texture_samplers(state); + err = _parse_texture_samplers(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* PARSE TEXTURES */ - err = _parse_textures(state); + err = _parse_textures(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* PARSE TEXTURES */ - err = _parse_materials(state); + err = _parse_materials(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); } /* PARSE SKINS */ - err = _parse_skins(state); + err = _parse_skins(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* DETERMINE SKELETONS */ - err = _determine_skeletons(state); + err = _determine_skeletons(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* CREATE SKELETONS */ - err = _create_skeletons(state); + err = _create_skeletons(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* CREATE SKINS */ - err = _create_skins(state); + err = _create_skins(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* PARSE MESHES (we have enough info now) */ - err = _parse_meshes(state); + err = _parse_meshes(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* PARSE LIGHTS */ - err = _parse_lights(state); + err = _parse_lights(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* PARSE CAMERAS */ - err = _parse_cameras(state); + err = _parse_cameras(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* PARSE ANIMATIONS */ - err = _parse_animations(state); + err = _parse_animations(p_state); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* ASSIGN SCENE NAMES */ - _assign_scene_names(state); + _assign_scene_names(p_state); Node3D *root = memnew(Node3D); - for (int32_t root_i = 0; root_i < state->root_nodes.size(); root_i++) { - _generate_scene_node(state, root, root, state->root_nodes[root_i]); + for (int32_t root_i = 0; root_i < p_state->root_nodes.size(); root_i++) { + _generate_scene_node(p_state, root, root, p_state->root_nodes[root_i]); } return OK; } -Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags, int32_t p_bake_fps, String p_base_path) { +Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags, String p_base_path) { // TODO Add missing texture and missing .bin file paths to r_missing_deps 2021-09-10 fire if (r_state == Ref<GLTFState>()) { r_state.instantiate(); @@ -7020,18 +7068,17 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint r_state->use_named_skin_binds = p_flags & GLTF_IMPORT_USE_NAMED_SKIN_BINDS; r_state->discard_meshes_and_materials = p_flags & GLTF_IMPORT_DISCARD_MESHES_AND_MATERIALS; Error err; - Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err); + Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err); ERR_FAIL_COND_V(err != OK, ERR_FILE_CANT_OPEN); - ERR_FAIL_NULL_V(f, ERR_FILE_CANT_OPEN); + ERR_FAIL_NULL_V(file, ERR_FILE_CANT_OPEN); String base_path = p_base_path; if (base_path.is_empty()) { base_path = p_path.get_base_dir(); } r_state->base_path = base_path; - err = _parse(r_state, base_path, f, p_bake_fps); + err = _parse(r_state, base_path, file); ERR_FAIL_COND_V(err != OK, err); - for (int32_t ext_i = 0; ext_i < document_extensions.size(); ext_i++) { - Ref<GLTFDocumentExtension> ext = document_extensions[ext_i]; + for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); err = ext->import_post_parse(r_state); ERR_FAIL_COND_V(err != OK, err); @@ -7039,22 +7086,21 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> r_state, uint return OK; } -Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> state) { - ERR_FAIL_NULL_V(state, ERR_PARSE_ERROR); - if (state->json.has("extensionsUsed")) { - Vector<String> ext_array = state->json["extensionsUsed"]; - state->extensions_used = ext_array; +Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> p_state) { + ERR_FAIL_NULL_V(p_state, ERR_PARSE_ERROR); + if (p_state->json.has("extensionsUsed")) { + Vector<String> ext_array = p_state->json["extensionsUsed"]; + p_state->extensions_used = ext_array; } - if (state->json.has("extensionsRequired")) { - Vector<String> ext_array = state->json["extensionsRequired"]; - state->extensions_required = ext_array; + if (p_state->json.has("extensionsRequired")) { + Vector<String> ext_array = p_state->json["extensionsRequired"]; + p_state->extensions_required = ext_array; } HashSet<String> supported_extensions; supported_extensions.insert("KHR_lights_punctual"); supported_extensions.insert("KHR_materials_pbrSpecularGlossiness"); supported_extensions.insert("KHR_texture_transform"); - for (int ext_i = 0; ext_i < document_extensions.size(); ext_i++) { - Ref<GLTFDocumentExtension> ext = document_extensions[ext_i]; + for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); Vector<String> ext_supported_extensions = ext->get_supported_extensions(); for (int i = 0; i < ext_supported_extensions.size(); ++i) { @@ -7062,9 +7108,9 @@ Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> state) { } } Error ret = Error::OK; - for (int i = 0; i < state->extensions_required.size(); i++) { - if (!supported_extensions.has(state->extensions_required[i])) { - ERR_PRINT("GLTF: Can't import file '" + state->filename + "', required extension '" + String(state->extensions_required[i]) + "' is not supported. Are you missing a GLTFDocumentExtension plugin?"); + for (int i = 0; i < p_state->extensions_required.size(); i++) { + if (!supported_extensions.has(p_state->extensions_required[i])) { + ERR_PRINT("GLTF: Can't import file '" + p_state->filename + "', required extension '" + String(p_state->extensions_required[i]) + "' is not supported. Are you missing a GLTFDocumentExtension plugin?"); ret = ERR_UNAVAILABLE; } } diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h index 62b6e29fe0..6e2d0e2fd4 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -31,26 +31,19 @@ #ifndef GLTF_DOCUMENT_H #define GLTF_DOCUMENT_H -#include "gltf_defines.h" -#include "structures/gltf_animation.h" - -#include "scene/3d/bone_attachment_3d.h" -#include "scene/3d/importer_mesh_instance_3d.h" -#include "scene/3d/mesh_instance_3d.h" -#include "scene/animation/animation_player.h" -#include "scene/resources/material.h" +#include "extensions/gltf_document_extension.h" #include "modules/modules_enabled.gen.h" // For csg, gridmap. class GLTFDocument : public Resource { GDCLASS(GLTFDocument, Resource); - TypedArray<GLTFDocumentExtension> document_extensions; + static Vector<Ref<GLTFDocumentExtension>> all_document_extensions; + Vector<Ref<GLTFDocumentExtension>> document_extensions; private: const float BAKE_FPS = 30.0f; public: - GLTFDocument(); const int32_t JOINT_GROUP_SIZE = 4; enum { @@ -76,203 +69,204 @@ protected: static void _bind_methods(); public: - void set_extensions(TypedArray<GLTFDocumentExtension> p_extensions); - TypedArray<GLTFDocumentExtension> get_extensions() const; + static void register_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension, bool p_first_priority = false); + static void unregister_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension); + static void unregister_all_gltf_document_extensions(); private: - void _build_parent_hierachy(Ref<GLTFState> state); + void _build_parent_hierachy(Ref<GLTFState> p_state); double _filter_number(double p_float); String _get_component_type_name(const uint32_t p_component); - int _get_component_type_size(const int component_type); - Error _parse_scenes(Ref<GLTFState> state); - Error _parse_nodes(Ref<GLTFState> state); + int _get_component_type_size(const int p_component_type); + Error _parse_scenes(Ref<GLTFState> p_state); + Error _parse_nodes(Ref<GLTFState> p_state); String _get_type_name(const GLTFType p_component); String _get_accessor_type_name(const GLTFType p_type); - String _gen_unique_name(Ref<GLTFState> state, const String &p_name); - String _sanitize_animation_name(const String &name); - String _gen_unique_animation_name(Ref<GLTFState> state, const String &p_name); - String _sanitize_bone_name(const String &name); - String _gen_unique_bone_name(Ref<GLTFState> state, - const GLTFSkeletonIndex skel_i, + String _gen_unique_name(Ref<GLTFState> p_state, const String &p_name); + String _sanitize_animation_name(const String &p_name); + String _gen_unique_animation_name(Ref<GLTFState> p_state, const String &p_name); + String _sanitize_bone_name(const String &p_name); + String _gen_unique_bone_name(Ref<GLTFState> p_state, + const GLTFSkeletonIndex p_skel_i, const String &p_name); - GLTFTextureIndex _set_texture(Ref<GLTFState> state, Ref<Texture2D> p_texture, + GLTFTextureIndex _set_texture(Ref<GLTFState> p_state, Ref<Texture2D> p_texture, StandardMaterial3D::TextureFilter p_filter_mode, bool p_repeats); - Ref<Texture2D> _get_texture(Ref<GLTFState> state, + Ref<Texture2D> _get_texture(Ref<GLTFState> p_state, const GLTFTextureIndex p_texture); - GLTFTextureSamplerIndex _set_sampler_for_mode(Ref<GLTFState> state, + GLTFTextureSamplerIndex _set_sampler_for_mode(Ref<GLTFState> p_state, StandardMaterial3D::TextureFilter p_filter_mode, bool p_repeats); - Ref<GLTFTextureSampler> _get_sampler_for_texture(Ref<GLTFState> state, + Ref<GLTFTextureSampler> _get_sampler_for_texture(Ref<GLTFState> p_state, const GLTFTextureIndex p_texture); - Error _parse_json(const String &p_path, Ref<GLTFState> state); - Error _parse_glb(Ref<FileAccess> f, Ref<GLTFState> state); - void _compute_node_heights(Ref<GLTFState> state); - Error _parse_buffers(Ref<GLTFState> state, const String &p_base_path); - Error _parse_buffer_views(Ref<GLTFState> state); + Error _parse_json(const String &p_path, Ref<GLTFState> p_state); + Error _parse_glb(Ref<FileAccess> p_file, Ref<GLTFState> p_state); + void _compute_node_heights(Ref<GLTFState> p_state); + Error _parse_buffers(Ref<GLTFState> p_state, const String &p_base_path); + Error _parse_buffer_views(Ref<GLTFState> p_state); GLTFType _get_type_from_str(const String &p_string); - Error _parse_accessors(Ref<GLTFState> state); - Error _decode_buffer_view(Ref<GLTFState> state, double *dst, + Error _parse_accessors(Ref<GLTFState> p_state); + Error _decode_buffer_view(Ref<GLTFState> p_state, double *p_dst, const GLTFBufferViewIndex p_buffer_view, - const int skip_every, const int skip_bytes, - const int element_size, const int count, - const GLTFType type, const int component_count, - const int component_type, const int component_size, - const bool normalized, const int byte_offset, - const bool for_vertex); - Vector<double> _decode_accessor(Ref<GLTFState> state, + const int p_skip_every, const int p_skip_bytes, + const int p_element_size, const int p_count, + const GLTFType p_type, const int p_component_count, + const int p_component_type, const int p_component_size, + const bool p_normalized, const int p_byte_offset, + const bool p_for_vertex); + Vector<double> _decode_accessor(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - Vector<float> _decode_accessor_as_floats(Ref<GLTFState> state, + Vector<float> _decode_accessor_as_floats(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - Vector<int> _decode_accessor_as_ints(Ref<GLTFState> state, + Vector<int> _decode_accessor_as_ints(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - Vector<Vector2> _decode_accessor_as_vec2(Ref<GLTFState> state, + Vector<Vector2> _decode_accessor_as_vec2(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - Vector<Vector3> _decode_accessor_as_vec3(Ref<GLTFState> state, + Vector<Vector3> _decode_accessor_as_vec3(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - Vector<Color> _decode_accessor_as_color(Ref<GLTFState> state, + Vector<Color> _decode_accessor_as_color(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - Vector<Quaternion> _decode_accessor_as_quaternion(Ref<GLTFState> state, + Vector<Quaternion> _decode_accessor_as_quaternion(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - Vector<Transform2D> _decode_accessor_as_xform2d(Ref<GLTFState> state, + Vector<Transform2D> _decode_accessor_as_xform2d(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - Vector<Basis> _decode_accessor_as_basis(Ref<GLTFState> state, + Vector<Basis> _decode_accessor_as_basis(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - Vector<Transform3D> _decode_accessor_as_xform(Ref<GLTFState> state, + Vector<Transform3D> _decode_accessor_as_xform(Ref<GLTFState> p_state, const GLTFAccessorIndex p_accessor, const bool p_for_vertex); - Error _parse_meshes(Ref<GLTFState> state); - Error _serialize_textures(Ref<GLTFState> state); - Error _serialize_texture_samplers(Ref<GLTFState> state); - Error _serialize_images(Ref<GLTFState> state, const String &p_path); - Error _serialize_lights(Ref<GLTFState> state); - Error _parse_images(Ref<GLTFState> state, const String &p_base_path); - Error _parse_textures(Ref<GLTFState> state); - Error _parse_texture_samplers(Ref<GLTFState> state); - Error _parse_materials(Ref<GLTFState> state); - void _set_texture_transform_uv1(const Dictionary &d, Ref<BaseMaterial3D> material); + Error _parse_meshes(Ref<GLTFState> p_state); + Error _serialize_textures(Ref<GLTFState> p_state); + Error _serialize_texture_samplers(Ref<GLTFState> p_state); + Error _serialize_images(Ref<GLTFState> p_state, const String &p_path); + Error _serialize_lights(Ref<GLTFState> p_state); + Error _parse_images(Ref<GLTFState> p_state, const String &p_base_path); + Error _parse_textures(Ref<GLTFState> p_state); + Error _parse_texture_samplers(Ref<GLTFState> p_state); + Error _parse_materials(Ref<GLTFState> p_state); + void _set_texture_transform_uv1(const Dictionary &d, Ref<BaseMaterial3D> p_material); void spec_gloss_to_rough_metal(Ref<GLTFSpecGloss> r_spec_gloss, Ref<BaseMaterial3D> p_material); static void spec_gloss_to_metal_base_color(const Color &p_specular_factor, const Color &p_diffuse, Color &r_base_color, float &r_metallic); - GLTFNodeIndex _find_highest_node(Ref<GLTFState> state, - const Vector<GLTFNodeIndex> &subset); - bool _capture_nodes_in_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin, - const GLTFNodeIndex node_index); - void _capture_nodes_for_multirooted_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin); - Error _expand_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin); - Error _verify_skin(Ref<GLTFState> state, Ref<GLTFSkin> skin); - Error _parse_skins(Ref<GLTFState> state); - Error _determine_skeletons(Ref<GLTFState> state); + GLTFNodeIndex _find_highest_node(Ref<GLTFState> p_state, + const Vector<GLTFNodeIndex> &p_subset); + bool _capture_nodes_in_skin(Ref<GLTFState> p_state, Ref<GLTFSkin> p_skin, + const GLTFNodeIndex p_node_index); + void _capture_nodes_for_multirooted_skin(Ref<GLTFState> p_state, Ref<GLTFSkin> p_skin); + Error _expand_skin(Ref<GLTFState> p_state, Ref<GLTFSkin> p_skin); + Error _verify_skin(Ref<GLTFState> p_state, Ref<GLTFSkin> p_skin); + Error _parse_skins(Ref<GLTFState> p_state); + Error _determine_skeletons(Ref<GLTFState> p_state); Error _reparent_non_joint_skeleton_subtrees( - Ref<GLTFState> state, Ref<GLTFSkeleton> skeleton, - const Vector<GLTFNodeIndex> &non_joints); - Error _determine_skeleton_roots(Ref<GLTFState> state, - const GLTFSkeletonIndex skel_i); - Error _create_skeletons(Ref<GLTFState> state); - Error _map_skin_joints_indices_to_skeleton_bone_indices(Ref<GLTFState> state); - Error _serialize_skins(Ref<GLTFState> state); - Error _create_skins(Ref<GLTFState> state); - bool _skins_are_same(const Ref<Skin> skin_a, const Ref<Skin> skin_b); - void _remove_duplicate_skins(Ref<GLTFState> state); - Error _serialize_cameras(Ref<GLTFState> state); - Error _parse_cameras(Ref<GLTFState> state); - Error _parse_lights(Ref<GLTFState> state); - Error _parse_animations(Ref<GLTFState> state); - Error _serialize_animations(Ref<GLTFState> state); - BoneAttachment3D *_generate_bone_attachment(Ref<GLTFState> state, - Skeleton3D *skeleton, - const GLTFNodeIndex node_index, - const GLTFNodeIndex bone_index); - ImporterMeshInstance3D *_generate_mesh_instance(Ref<GLTFState> state, const GLTFNodeIndex node_index); - Camera3D *_generate_camera(Ref<GLTFState> state, const GLTFNodeIndex node_index); - Light3D *_generate_light(Ref<GLTFState> state, const GLTFNodeIndex node_index); - Node3D *_generate_spatial(Ref<GLTFState> state, const GLTFNodeIndex node_index); - void _assign_scene_names(Ref<GLTFState> state); + Ref<GLTFState> p_state, Ref<GLTFSkeleton> p_skeleton, + const Vector<GLTFNodeIndex> &p_non_joints); + Error _determine_skeleton_roots(Ref<GLTFState> p_state, + const GLTFSkeletonIndex p_skel_i); + Error _create_skeletons(Ref<GLTFState> p_state); + Error _map_skin_joints_indices_to_skeleton_bone_indices(Ref<GLTFState> p_state); + Error _serialize_skins(Ref<GLTFState> p_state); + Error _create_skins(Ref<GLTFState> p_state); + bool _skins_are_same(const Ref<Skin> p_skin_a, const Ref<Skin> p_skin_b); + void _remove_duplicate_skins(Ref<GLTFState> p_state); + Error _serialize_cameras(Ref<GLTFState> p_state); + Error _parse_cameras(Ref<GLTFState> p_state); + Error _parse_lights(Ref<GLTFState> p_state); + Error _parse_animations(Ref<GLTFState> p_state); + Error _serialize_animations(Ref<GLTFState> p_state); + BoneAttachment3D *_generate_bone_attachment(Ref<GLTFState> p_state, + Skeleton3D *p_skeleton, + const GLTFNodeIndex p_node_index, + const GLTFNodeIndex p_bone_index); + ImporterMeshInstance3D *_generate_mesh_instance(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index); + Camera3D *_generate_camera(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index); + Light3D *_generate_light(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index); + Node3D *_generate_spatial(Ref<GLTFState> p_state, const GLTFNodeIndex p_node_index); + void _assign_scene_names(Ref<GLTFState> p_state); template <class T> T _interpolate_track(const Vector<real_t> &p_times, const Vector<T> &p_values, const float p_time, const GLTFAnimation::Interpolation p_interp); - GLTFAccessorIndex _encode_accessor_as_quaternions(Ref<GLTFState> state, + GLTFAccessorIndex _encode_accessor_as_quaternions(Ref<GLTFState> p_state, const Vector<Quaternion> p_attribs, const bool p_for_vertex); - GLTFAccessorIndex _encode_accessor_as_weights(Ref<GLTFState> state, + GLTFAccessorIndex _encode_accessor_as_weights(Ref<GLTFState> p_state, const Vector<Color> p_attribs, const bool p_for_vertex); - GLTFAccessorIndex _encode_accessor_as_joints(Ref<GLTFState> state, + GLTFAccessorIndex _encode_accessor_as_joints(Ref<GLTFState> p_state, const Vector<Color> p_attribs, const bool p_for_vertex); - GLTFAccessorIndex _encode_accessor_as_floats(Ref<GLTFState> state, + GLTFAccessorIndex _encode_accessor_as_floats(Ref<GLTFState> p_state, const Vector<real_t> p_attribs, const bool p_for_vertex); - GLTFAccessorIndex _encode_accessor_as_vec2(Ref<GLTFState> state, + GLTFAccessorIndex _encode_accessor_as_vec2(Ref<GLTFState> p_state, const Vector<Vector2> p_attribs, const bool p_for_vertex); - void _calc_accessor_vec2_min_max(int i, const int element_count, Vector<double> &type_max, Vector2 attribs, Vector<double> &type_min) { - if (i == 0) { - for (int32_t type_i = 0; type_i < element_count; type_i++) { - type_max.write[type_i] = attribs[(i * element_count) + type_i]; - type_min.write[type_i] = attribs[(i * element_count) + type_i]; + void _calc_accessor_vec2_min_max(int p_i, const int p_element_count, Vector<double> &p_type_max, Vector2 p_attribs, Vector<double> &p_type_min) { + if (p_i == 0) { + for (int32_t type_i = 0; type_i < p_element_count; type_i++) { + p_type_max.write[type_i] = p_attribs[(p_i * p_element_count) + type_i]; + p_type_min.write[type_i] = p_attribs[(p_i * p_element_count) + type_i]; } } - for (int32_t type_i = 0; type_i < element_count; type_i++) { - type_max.write[type_i] = MAX(attribs[(i * element_count) + type_i], type_max[type_i]); - type_min.write[type_i] = MIN(attribs[(i * element_count) + type_i], type_min[type_i]); - type_max.write[type_i] = _filter_number(type_max.write[type_i]); - type_min.write[type_i] = _filter_number(type_min.write[type_i]); + for (int32_t type_i = 0; type_i < p_element_count; type_i++) { + p_type_max.write[type_i] = MAX(p_attribs[(p_i * p_element_count) + type_i], p_type_max[type_i]); + p_type_min.write[type_i] = MIN(p_attribs[(p_i * p_element_count) + type_i], p_type_min[type_i]); + p_type_max.write[type_i] = _filter_number(p_type_max.write[type_i]); + p_type_min.write[type_i] = _filter_number(p_type_min.write[type_i]); } } - GLTFAccessorIndex _encode_accessor_as_vec3(Ref<GLTFState> state, + GLTFAccessorIndex _encode_accessor_as_vec3(Ref<GLTFState> p_state, const Vector<Vector3> p_attribs, const bool p_for_vertex); - GLTFAccessorIndex _encode_accessor_as_color(Ref<GLTFState> state, + GLTFAccessorIndex _encode_accessor_as_color(Ref<GLTFState> p_state, const Vector<Color> p_attribs, const bool p_for_vertex); void _calc_accessor_min_max(int p_i, const int p_element_count, Vector<double> &p_type_max, Vector<double> p_attribs, Vector<double> &p_type_min); - GLTFAccessorIndex _encode_accessor_as_ints(Ref<GLTFState> state, + GLTFAccessorIndex _encode_accessor_as_ints(Ref<GLTFState> p_state, const Vector<int32_t> p_attribs, const bool p_for_vertex); - GLTFAccessorIndex _encode_accessor_as_xform(Ref<GLTFState> state, + GLTFAccessorIndex _encode_accessor_as_xform(Ref<GLTFState> p_state, const Vector<Transform3D> p_attribs, const bool p_for_vertex); - Error _encode_buffer_view(Ref<GLTFState> state, const double *src, - const int count, const GLTFType type, - const int component_type, const bool normalized, - const int byte_offset, const bool for_vertex, + Error _encode_buffer_view(Ref<GLTFState> p_state, const double *p_src, + const int p_count, const GLTFType p_type, + const int p_component_type, const bool p_normalized, + const int p_byte_offset, const bool p_for_vertex, GLTFBufferViewIndex &r_accessor); - Error _encode_accessors(Ref<GLTFState> state); - Error _encode_buffer_views(Ref<GLTFState> state); - Error _serialize_materials(Ref<GLTFState> state); - Error _serialize_meshes(Ref<GLTFState> state); - Error _serialize_nodes(Ref<GLTFState> state); - Error _serialize_scenes(Ref<GLTFState> state); + Error _encode_accessors(Ref<GLTFState> p_state); + Error _encode_buffer_views(Ref<GLTFState> p_state); + Error _serialize_materials(Ref<GLTFState> p_state); + Error _serialize_meshes(Ref<GLTFState> p_state); + Error _serialize_nodes(Ref<GLTFState> p_state); + Error _serialize_scenes(Ref<GLTFState> p_state); String interpolation_to_string(const GLTFAnimation::Interpolation p_interp); - GLTFAnimation::Track _convert_animation_track(Ref<GLTFState> state, + GLTFAnimation::Track _convert_animation_track(Ref<GLTFState> p_state, GLTFAnimation::Track p_track, Ref<Animation> p_animation, int32_t p_track_i, GLTFNodeIndex p_node_i); - Error _encode_buffer_bins(Ref<GLTFState> state, const String &p_path); - Error _encode_buffer_glb(Ref<GLTFState> state, const String &p_path); - PackedByteArray _serialize_glb_buffer(Ref<GLTFState> state, Error *r_err); + Error _encode_buffer_bins(Ref<GLTFState> p_state, const String &p_path); + Error _encode_buffer_glb(Ref<GLTFState> p_state, const String &p_path); + PackedByteArray _serialize_glb_buffer(Ref<GLTFState> p_state, Error *r_err); Dictionary _serialize_texture_transform_uv1(Ref<BaseMaterial3D> p_material); Dictionary _serialize_texture_transform_uv2(Ref<BaseMaterial3D> p_material); - Error _serialize_version(Ref<GLTFState> state); - Error _serialize_file(Ref<GLTFState> state, const String p_path); - Error _serialize_gltf_extensions(Ref<GLTFState> state) const; + Error _serialize_version(Ref<GLTFState> p_state); + Error _serialize_file(Ref<GLTFState> p_state, const String p_path); + Error _serialize_gltf_extensions(Ref<GLTFState> p_state) const; public: // https://www.itu.int/rec/R-REC-BT.601 @@ -284,90 +278,90 @@ public: private: // https://github.com/microsoft/glTF-SDK/blob/master/GLTFSDK/Source/PBRUtils.cpp#L9 // https://bghgary.github.io/glTF/convert-between-workflows-bjs/js/babylon.pbrUtilities.js - static float solve_metallic(float p_dielectric_specular, float diffuse, - float specular, + static float solve_metallic(float p_dielectric_specular, float p_diffuse, + float p_specular, float p_one_minus_specular_strength); static float get_perceived_brightness(const Color p_color); static float get_max_component(const Color &p_color); public: - Error append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags = 0, int32_t p_bake_fps = 30, String p_base_path = String()); - Error append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> r_state, uint32_t p_flags = 0, int32_t p_bake_fps = 30); - Error append_from_scene(Node *p_node, Ref<GLTFState> r_state, uint32_t p_flags = 0, int32_t p_bake_fps = 30); + Error append_from_file(String p_path, Ref<GLTFState> r_state, uint32_t p_flags = 0, String p_base_path = String()); + Error append_from_buffer(PackedByteArray p_bytes, String p_base_path, Ref<GLTFState> r_state, uint32_t p_flags = 0); + Error append_from_scene(Node *p_node, Ref<GLTFState> r_state, uint32_t p_flags = 0); public: - Node *generate_scene(Ref<GLTFState> state, int32_t p_bake_fps = 30.0f); - PackedByteArray generate_buffer(Ref<GLTFState> state); - Error write_to_filesystem(Ref<GLTFState> state, const String &p_path); + Node *generate_scene(Ref<GLTFState> p_state, float p_bake_fps = 30.0f, bool p_trimming = false); + PackedByteArray generate_buffer(Ref<GLTFState> p_state); + Error write_to_filesystem(Ref<GLTFState> p_state, const String &p_path); public: - Error _parse_gltf_state(Ref<GLTFState> state, const String &p_search_path, float p_bake_fps); - Error _parse_gltf_extensions(Ref<GLTFState> state); - void _process_mesh_instances(Ref<GLTFState> state, Node *scene_root); - void _generate_scene_node(Ref<GLTFState> state, Node *scene_parent, - Node3D *scene_root, - const GLTFNodeIndex node_index); - void _generate_skeleton_bone_node(Ref<GLTFState> state, Node *scene_parent, Node3D *scene_root, const GLTFNodeIndex node_index); - void _import_animation(Ref<GLTFState> state, AnimationPlayer *ap, - const GLTFAnimationIndex index, const int bake_fps); - void _convert_mesh_instances(Ref<GLTFState> state); - GLTFCameraIndex _convert_camera(Ref<GLTFState> state, Camera3D *p_camera); - void _convert_light_to_gltf(Light3D *light, Ref<GLTFState> state, Ref<GLTFNode> gltf_node); - GLTFLightIndex _convert_light(Ref<GLTFState> state, Light3D *p_light); - void _convert_spatial(Ref<GLTFState> state, Node3D *p_spatial, Ref<GLTFNode> p_node); - void _convert_scene_node(Ref<GLTFState> state, Node *p_current, + Error _parse_gltf_state(Ref<GLTFState> p_state, const String &p_search_path); + Error _parse_gltf_extensions(Ref<GLTFState> p_state); + void _process_mesh_instances(Ref<GLTFState> p_state, Node *p_scene_root); + void _generate_scene_node(Ref<GLTFState> p_state, Node *p_scene_parent, + Node3D *p_scene_root, + const GLTFNodeIndex p_node_index); + void _generate_skeleton_bone_node(Ref<GLTFState> p_state, Node *p_scene_parent, Node3D *p_scene_root, const GLTFNodeIndex p_node_index); + void _import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, + const GLTFAnimationIndex p_index, const float p_bake_fps, const bool p_trimming); + void _convert_mesh_instances(Ref<GLTFState> p_state); + GLTFCameraIndex _convert_camera(Ref<GLTFState> p_state, Camera3D *p_camera); + void _convert_light_to_gltf(Light3D *p_light, Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node); + GLTFLightIndex _convert_light(Ref<GLTFState> p_state, Light3D *p_light); + void _convert_spatial(Ref<GLTFState> p_state, Node3D *p_spatial, Ref<GLTFNode> p_node); + void _convert_scene_node(Ref<GLTFState> p_state, Node *p_current, const GLTFNodeIndex p_gltf_current, const GLTFNodeIndex p_gltf_root); #ifdef MODULE_CSG_ENABLED - void _convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeIndex p_gltf_parent, Ref<GLTFNode> gltf_node, Ref<GLTFState> state); + void _convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeIndex p_gltf_parent, Ref<GLTFNode> p_gltf_node, Ref<GLTFState> p_state); #endif // MODULE_CSG_ENABLED - void _create_gltf_node(Ref<GLTFState> state, + void _create_gltf_node(Ref<GLTFState> p_state, Node *p_scene_parent, - GLTFNodeIndex current_node_i, + GLTFNodeIndex p_current_node_i, GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_gltf_node, - Ref<GLTFNode> gltf_node); + Ref<GLTFNode> p_gltf_node); void _convert_animation_player_to_gltf( - AnimationPlayer *animation_player, Ref<GLTFState> state, + AnimationPlayer *p_animation_player, Ref<GLTFState> p_state, GLTFNodeIndex p_gltf_current, GLTFNodeIndex p_gltf_root_index, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent); - void _check_visibility(Node *p_node, bool &retflag); - void _convert_camera_to_gltf(Camera3D *camera, Ref<GLTFState> state, - Ref<GLTFNode> gltf_node); + void _check_visibility(Node *p_node, bool &r_retflag); + void _convert_camera_to_gltf(Camera3D *p_camera, Ref<GLTFState> p_state, + Ref<GLTFNode> p_gltf_node); #ifdef MODULE_GRIDMAP_ENABLED void _convert_grid_map_to_gltf( GridMap *p_grid_map, GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_node_index, - Ref<GLTFNode> gltf_node, Ref<GLTFState> state); + Ref<GLTFNode> p_gltf_node, Ref<GLTFState> p_state); #endif // MODULE_GRIDMAP_ENABLED void _convert_multi_mesh_instance_to_gltf( MultiMeshInstance3D *p_multi_mesh_instance, GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_node_index, - Ref<GLTFNode> gltf_node, Ref<GLTFState> state); + Ref<GLTFNode> p_gltf_node, Ref<GLTFState> p_state); void _convert_skeleton_to_gltf( - Skeleton3D *p_scene_parent, Ref<GLTFState> state, + Skeleton3D *p_scene_parent, Ref<GLTFState> p_state, GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_node_index, - Ref<GLTFNode> gltf_node); + Ref<GLTFNode> p_gltf_node); void _convert_bone_attachment_to_gltf(BoneAttachment3D *p_bone_attachment, - Ref<GLTFState> state, + Ref<GLTFState> p_state, GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_node_index, - Ref<GLTFNode> gltf_node); + Ref<GLTFNode> p_gltf_node); void _convert_mesh_instance_to_gltf(MeshInstance3D *p_mesh_instance, - Ref<GLTFState> state, - Ref<GLTFNode> gltf_node); - GLTFMeshIndex _convert_mesh_to_gltf(Ref<GLTFState> state, + Ref<GLTFState> p_state, + Ref<GLTFNode> p_gltf_node); + GLTFMeshIndex _convert_mesh_to_gltf(Ref<GLTFState> p_state, MeshInstance3D *p_mesh_instance); - void _convert_animation(Ref<GLTFState> state, AnimationPlayer *ap, + void _convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, String p_animation_track_name); - Error _serialize(Ref<GLTFState> state, const String &p_path); - Error _parse(Ref<GLTFState> state, String p_path, Ref<FileAccess> f, int p_bake_fps); + Error _serialize(Ref<GLTFState> p_state, const String &p_path); + Error _parse(Ref<GLTFState> p_state, String p_path, Ref<FileAccess> p_file); }; #endif // GLTF_DOCUMENT_H diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp index ac5665e396..9f6cb20935 100644 --- a/modules/gltf/gltf_state.cpp +++ b/modules/gltf/gltf_state.cpp @@ -30,6 +30,8 @@ #include "gltf_state.h" +#include "gltf_template_convert.h" + void GLTFState::_bind_methods() { ClassDB::bind_method(D_METHOD("add_used_extension", "extension_name", "required"), &GLTFState::add_used_extension); ClassDB::bind_method(D_METHOD("get_json"), &GLTFState::get_json); @@ -209,11 +211,11 @@ void GLTFState::set_meshes(TypedArray<GLTFMesh> p_meshes) { GLTFTemplateConvert::set_from_array(meshes, p_meshes); } -TypedArray<BaseMaterial3D> GLTFState::get_materials() { +TypedArray<Material> GLTFState::get_materials() { return GLTFTemplateConvert::to_array(materials); } -void GLTFState::set_materials(TypedArray<BaseMaterial3D> p_materials) { +void GLTFState::set_materials(TypedArray<Material> p_materials) { GLTFTemplateConvert::set_from_array(materials, p_materials); } diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h index e24017b0fd..e264da69e0 100644 --- a/modules/gltf/gltf_state.h +++ b/modules/gltf/gltf_state.h @@ -32,7 +32,6 @@ #define GLTF_STATE_H #include "extensions/gltf_light.h" -#include "gltf_template_convert.h" #include "structures/gltf_accessor.h" #include "structures/gltf_animation.h" #include "structures/gltf_buffer_view.h" @@ -44,10 +43,6 @@ #include "structures/gltf_texture.h" #include "structures/gltf_texture_sampler.h" -#include "core/templates/rb_map.h" -#include "scene/animation/animation_player.h" -#include "scene/resources/texture.h" - class GLTFState : public Resource { GDCLASS(GLTFState, Resource); friend class GLTFDocument; @@ -72,8 +67,8 @@ class GLTFState : public Resource { Vector<Ref<GLTFMesh>> meshes; // meshes are loaded directly, no reason not to. Vector<AnimationPlayer *> animation_players; - HashMap<Ref<BaseMaterial3D>, GLTFMaterialIndex> material_cache; - Vector<Ref<BaseMaterial3D>> materials; + HashMap<Ref<Material>, GLTFMaterialIndex> material_cache; + Vector<Ref<Material>> materials; String scene_name; Vector<int> root_nodes; @@ -138,8 +133,8 @@ public: TypedArray<GLTFMesh> get_meshes(); void set_meshes(TypedArray<GLTFMesh> p_meshes); - TypedArray<BaseMaterial3D> get_materials(); - void set_materials(TypedArray<BaseMaterial3D> p_materials); + TypedArray<Material> get_materials(); + void set_materials(TypedArray<Material> p_materials); String get_scene_name(); void set_scene_name(String p_scene_name); @@ -194,21 +189,6 @@ public: Variant get_additional_data(const StringName &p_extension_name); void set_additional_data(const StringName &p_extension_name, Variant p_additional_data); - - //void set_scene_nodes(RBMap<GLTFNodeIndex, Node *> p_scene_nodes) { - // this->scene_nodes = p_scene_nodes; - //} - - //void set_animation_players(Vector<AnimationPlayer *> p_animation_players) { - // this->animation_players = p_animation_players; - //} - - //RBMap<Ref<Material>, GLTFMaterialIndex> get_material_cache() { - // return this->material_cache; - //} - //void set_material_cache(RBMap<Ref<Material>, GLTFMaterialIndex> p_material_cache) { - // this->material_cache = p_material_cache; - //} }; #endif // GLTF_STATE_H diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp index b9027f6e3d..cd7a23fbb2 100644 --- a/modules/gltf/register_types.cpp +++ b/modules/gltf/register_types.cpp @@ -30,24 +30,9 @@ #include "register_types.h" -#ifndef _3D_DISABLED - -#include "extensions/gltf_light.h" +#include "extensions/gltf_document_extension_convert_importer_mesh.h" #include "extensions/gltf_spec_gloss.h" #include "gltf_document.h" -#include "gltf_document_extension.h" -#include "gltf_document_extension_convert_importer_mesh.h" -#include "gltf_state.h" -#include "structures/gltf_accessor.h" -#include "structures/gltf_animation.h" -#include "structures/gltf_buffer_view.h" -#include "structures/gltf_camera.h" -#include "structures/gltf_mesh.h" -#include "structures/gltf_node.h" -#include "structures/gltf_skeleton.h" -#include "structures/gltf_skin.h" -#include "structures/gltf_texture.h" -#include "structures/gltf_texture_sampler.h" #ifdef TOOLS_ENABLED #include "core/config/project_settings.h" @@ -109,6 +94,11 @@ static void _editor_init() { } #endif // TOOLS_ENABLED +#define GLTF_REGISTER_DOCUMENT_EXTENSION(m_doc_ext_class) \ + Ref<m_doc_ext_class> extension_##m_doc_ext_class; \ + extension_##m_doc_ext_class.instantiate(); \ + GLTFDocument::register_gltf_document_extension(extension_##m_doc_ext_class); + void initialize_gltf_module(ModuleInitializationLevel p_level) { if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) { // glTF API available at runtime. @@ -128,6 +118,11 @@ void initialize_gltf_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(GLTFState); GDREGISTER_CLASS(GLTFTexture); GDREGISTER_CLASS(GLTFTextureSampler); + // Register GLTFDocumentExtension classes with GLTFDocument. + bool is_editor = ::Engine::get_singleton()->is_editor_hint(); + if (!is_editor) { + GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionConvertImporterMesh); + } } #ifdef TOOLS_ENABLED @@ -161,6 +156,5 @@ void uninitialize_gltf_module(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { return; } + GLTFDocument::unregister_all_gltf_document_extensions(); } - -#endif // _3D_DISABLED diff --git a/modules/gltf/structures/gltf_accessor.h b/modules/gltf/structures/gltf_accessor.h index bfb71d57fe..8e4bb2d3f9 100644 --- a/modules/gltf/structures/gltf_accessor.h +++ b/modules/gltf/structures/gltf_accessor.h @@ -31,9 +31,8 @@ #ifndef GLTF_ACCESSOR_H #define GLTF_ACCESSOR_H -#include "core/io/resource.h" - #include "../gltf_defines.h" +#include "core/io/resource.h" struct GLTFAccessor : public Resource { GDCLASS(GLTFAccessor, Resource); diff --git a/modules/gltf/structures/gltf_animation.h b/modules/gltf/structures/gltf_animation.h index 3777f579f6..fc535631bb 100644 --- a/modules/gltf/structures/gltf_animation.h +++ b/modules/gltf/structures/gltf_animation.h @@ -31,7 +31,7 @@ #ifndef GLTF_ANIMATION_H #define GLTF_ANIMATION_H -#include "core/io/resource.h" +#include "scene/animation/animation_player.h" class GLTFAnimation : public Resource { GDCLASS(GLTFAnimation, Resource); diff --git a/modules/gltf/structures/gltf_buffer_view.cpp b/modules/gltf/structures/gltf_buffer_view.cpp index ba19ed8628..a15141225b 100644 --- a/modules/gltf/structures/gltf_buffer_view.cpp +++ b/modules/gltf/structures/gltf_buffer_view.cpp @@ -30,8 +30,6 @@ #include "gltf_buffer_view.h" -#include "../gltf_document_extension.h" - void GLTFBufferView::_bind_methods() { ClassDB::bind_method(D_METHOD("get_buffer"), &GLTFBufferView::get_buffer); ClassDB::bind_method(D_METHOD("set_buffer", "buffer"), &GLTFBufferView::set_buffer); diff --git a/modules/gltf/structures/gltf_camera.cpp b/modules/gltf/structures/gltf_camera.cpp index 212b9b80c8..7a5ab2763c 100644 --- a/modules/gltf/structures/gltf_camera.cpp +++ b/modules/gltf/structures/gltf_camera.cpp @@ -30,6 +30,8 @@ #include "gltf_camera.h" +#include "scene/3d/camera_3d.h" + void GLTFCamera::_bind_methods() { ClassDB::bind_static_method("GLTFCamera", D_METHOD("from_node", "camera_node"), &GLTFCamera::from_node); ClassDB::bind_method(D_METHOD("to_node"), &GLTFCamera::to_node); diff --git a/modules/gltf/structures/gltf_camera.h b/modules/gltf/structures/gltf_camera.h index 50ae10e17a..5e8a1da5f7 100644 --- a/modules/gltf/structures/gltf_camera.h +++ b/modules/gltf/structures/gltf_camera.h @@ -32,7 +32,8 @@ #define GLTF_CAMERA_H #include "core/io/resource.h" -#include "scene/3d/camera_3d.h" + +class Camera3D; // Reference and test file: // https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_015_SimpleCameras.md @@ -64,7 +65,7 @@ public: real_t get_depth_near() const { return depth_near; } void set_depth_near(real_t p_val) { depth_near = p_val; } - static Ref<GLTFCamera> from_node(const Camera3D *p_light); + static Ref<GLTFCamera> from_node(const Camera3D *p_camera); Camera3D *to_node() const; static Ref<GLTFCamera> from_dictionary(const Dictionary p_dictionary); diff --git a/modules/gltf/structures/gltf_mesh.h b/modules/gltf/structures/gltf_mesh.h index 2fa37fd727..92722ce75c 100644 --- a/modules/gltf/structures/gltf_mesh.h +++ b/modules/gltf/structures/gltf_mesh.h @@ -31,10 +31,8 @@ #ifndef GLTF_MESH_H #define GLTF_MESH_H -#include "core/io/resource.h" -#include "scene/3d/importer_mesh_instance_3d.h" +#include "../gltf_defines.h" #include "scene/resources/importer_mesh.h" -#include "scene/resources/mesh.h" class GLTFMesh : public Resource { GDCLASS(GLTFMesh, Resource); diff --git a/modules/gltf/structures/gltf_texture_sampler.h b/modules/gltf/structures/gltf_texture_sampler.h index 3fad31bbee..7bb7cd62e3 100644 --- a/modules/gltf/structures/gltf_texture_sampler.h +++ b/modules/gltf/structures/gltf_texture_sampler.h @@ -31,7 +31,6 @@ #ifndef GLTF_TEXTURE_SAMPLER_H #define GLTF_TEXTURE_SAMPLER_H -#include "core/io/resource.h" #include "scene/resources/material.h" class GLTFTextureSampler : public Resource { diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml index 0f3662c3cf..bd5c938364 100644 --- a/modules/gridmap/doc_classes/GridMap.xml +++ b/modules/gridmap/doc_classes/GridMap.xml @@ -25,12 +25,14 @@ <method name="clear_baked_meshes"> <return type="void" /> <description> + Clears all baked meshes. See [method make_baked_meshes]. </description> </method> <method name="get_bake_mesh_instance"> <return type="RID" /> <param index="0" name="idx" type="int" /> <description> + Returns [RID] of a baked mesh with the given [param idx]. </description> </method> <method name="get_bake_meshes"> @@ -133,6 +135,7 @@ <param index="0" name="gen_lightmap_uv" type="bool" default="false" /> <param index="1" name="lightmap_uv_texel_size" type="float" default="0.1" /> <description> + Bakes lightmap data for all meshes in the assigned [MeshLibrary]. </description> </method> <method name="map_to_local" qualifiers="const"> @@ -146,6 +149,7 @@ <return type="void" /> <param index="0" name="resource" type="Resource" /> <description> + Notifies the [GridMap] about changed resource and recreates octant data. </description> </method> <method name="set_cell_item"> diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp index 9c6cbebf0e..89ef9ddd90 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.cpp +++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp @@ -40,6 +40,8 @@ #include "editor/editor_undo_redo_manager.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "scene/3d/camera_3d.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/separator.h" #include "scene/main/window.h" void GridMapEditor::_node_removed(Node *p_node) { @@ -459,6 +461,7 @@ void GridMapEditor::_delete_selection() { return; } + Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo(); undo_redo->create_action(TTR("GridMap Delete Selection")); for (int i = selection.begin.x; i <= selection.end.x; i++) { for (int j = selection.begin.y; j <= selection.end.y; j++) { @@ -479,6 +482,7 @@ void GridMapEditor::_fill_selection() { return; } + Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo(); undo_redo->create_action(TTR("GridMap Fill Selection")); for (int i = selection.begin.x; i <= selection.end.x; i++) { for (int j = selection.begin.y; j <= selection.end.y; j++) { @@ -572,6 +576,7 @@ void GridMapEditor::_do_paste() { rot = node->get_basis_with_orthogonal_index(paste_indicator.orientation); Vector3 ofs = paste_indicator.current - paste_indicator.click; + Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo(); undo_redo->create_action(TTR("GridMap Paste Selection")); for (const ClipboardItem &item : clipboard_items) { @@ -659,6 +664,7 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D } else { if ((mb->get_button_index() == MouseButton::RIGHT && input_action == INPUT_ERASE) || (mb->get_button_index() == MouseButton::LEFT && input_action == INPUT_PAINT)) { if (set_items.size()) { + Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo(); undo_redo->create_action(TTR("GridMap Paint")); for (const SetItem &si : set_items) { undo_redo->add_do_method(node, "set_cell_item", si.position, si.new_value, si.new_orientation); @@ -680,6 +686,7 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D } if (mb->get_button_index() == MouseButton::LEFT && input_action == INPUT_SELECT) { + Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo(); undo_redo->create_action(TTR("GridMap Selection")); undo_redo->add_do_method(this, "_set_selection", selection.active, selection.begin, selection.end); undo_redo->add_undo_method(this, "_set_selection", last_selection.active, last_selection.begin, last_selection.end); @@ -1142,8 +1149,6 @@ void GridMapEditor::_bind_methods() { } GridMapEditor::GridMapEditor() { - undo_redo = EditorNode::get_singleton()->get_undo_redo(); - int mw = EDITOR_DEF("editors/grid_map/palette_min_width", 230); Control *ec = memnew(Control); ec->set_custom_minimum_size(Size2(mw, 0) * EDSCALE); diff --git a/modules/gridmap/editor/grid_map_editor_plugin.h b/modules/gridmap/editor/grid_map_editor_plugin.h index 6fd38d9445..1cf2e4cb89 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.h +++ b/modules/gridmap/editor/grid_map_editor_plugin.h @@ -35,11 +35,13 @@ #include "../grid_map.h" #include "editor/editor_plugin.h" +#include "scene/gui/box_container.h" #include "scene/gui/item_list.h" #include "scene/gui/slider.h" #include "scene/gui/spin_box.h" -class EditorUndoRedoManager; +class ConfirmationDialog; +class MenuButton; class Node3DEditorPlugin; class GridMapEditor : public VBoxContainer { @@ -63,7 +65,6 @@ class GridMapEditor : public VBoxContainer { DISPLAY_LIST }; - Ref<EditorUndoRedoManager> undo_redo; InputAction input_action = INPUT_NONE; Panel *panel = nullptr; MenuButton *options = nullptr; diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index de50e9ea1e..06ad806afc 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -656,6 +656,7 @@ bool GridMap::_octant_update(const OctantKey &p_key) { if (bake_navigation) { RID region = NavigationServer3D::get_singleton()->region_create(); + NavigationServer3D::get_singleton()->region_set_owner_id(region, get_instance_id()); NavigationServer3D::get_singleton()->region_set_navigation_layers(region, navigation_layers); NavigationServer3D::get_singleton()->region_set_navmesh(region, navmesh); NavigationServer3D::get_singleton()->region_set_transform(region, get_global_transform() * nm.xform); @@ -779,6 +780,7 @@ void GridMap::_octant_enter_world(const OctantKey &p_key) { Ref<NavigationMesh> nm = mesh_library->get_item_navmesh(cell_map[F.key].item); if (nm.is_valid()) { RID region = NavigationServer3D::get_singleton()->region_create(); + NavigationServer3D::get_singleton()->region_set_owner_id(region, get_instance_id()); NavigationServer3D::get_singleton()->region_set_navigation_layers(region, navigation_layers); NavigationServer3D::get_singleton()->region_set_navmesh(region, nm); NavigationServer3D::get_singleton()->region_set_transform(region, get_global_transform() * F.value.xform); diff --git a/modules/lightmapper_rd/register_types.cpp b/modules/lightmapper_rd/register_types.cpp index 0e0330c1a1..ed223e1faa 100644 --- a/modules/lightmapper_rd/register_types.cpp +++ b/modules/lightmapper_rd/register_types.cpp @@ -57,6 +57,7 @@ void initialize_lightmapper_rd_module(ModuleInitializationLevel p_level) { GLOBAL_DEF("rendering/lightmapping/bake_quality/high_quality_probe_ray_count", 512); GLOBAL_DEF("rendering/lightmapping/bake_quality/ultra_quality_probe_ray_count", 2048); GLOBAL_DEF("rendering/lightmapping/bake_performance/max_rays_per_probe_pass", 64); + GLOBAL_DEF("rendering/lightmapping/primitive_meshes/texel_size", 0.2); #ifndef _3D_DISABLED GDREGISTER_CLASS(LightmapperRD); Lightmapper::create_gpu = create_lightmapper_rd; diff --git a/modules/mbedtls/packet_peer_mbed_dtls.cpp b/modules/mbedtls/packet_peer_mbed_dtls.cpp index e84d95773d..e658668355 100644 --- a/modules/mbedtls/packet_peer_mbed_dtls.cpp +++ b/modules/mbedtls/packet_peer_mbed_dtls.cpp @@ -118,7 +118,6 @@ Error PacketPeerMbedDTLS::connect_to_peer(Ref<PacketPeerUDP> p_base, bool p_vali ERR_FAIL_COND_V(!p_base.is_valid() || !p_base->is_socket_connected(), ERR_INVALID_PARAMETER); base = p_base; - int ret = 0; int authmode = p_validate_certs ? MBEDTLS_SSL_VERIFY_REQUIRED : MBEDTLS_SSL_VERIFY_NONE; Error err = tls_ctx->init_client(MBEDTLS_SSL_TRANSPORT_DATAGRAM, authmode, p_ca_certs); @@ -130,7 +129,7 @@ Error PacketPeerMbedDTLS::connect_to_peer(Ref<PacketPeerUDP> p_base, bool p_vali status = STATUS_HANDSHAKING; - if ((ret = _do_handshake()) != OK) { + if (_do_handshake() != OK) { status = STATUS_ERROR_HOSTNAME_MISMATCH; return FAILED; } @@ -158,7 +157,7 @@ Error PacketPeerMbedDTLS::accept_peer(Ref<PacketPeerUDP> p_base, Ref<CryptoKey> status = STATUS_HANDSHAKING; - if ((ret = _do_handshake()) != OK) { + if (_do_handshake() != OK) { status = STATUS_ERROR; return FAILED; } @@ -175,7 +174,7 @@ Error PacketPeerMbedDTLS::put_packet(const uint8_t *p_buffer, int p_bytes) { int ret = mbedtls_ssl_write(tls_ctx->get_context(), p_buffer, p_bytes); if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) { - ret = 0; // non blocking io + // Non blocking io. } else if (ret <= 0) { TLSContextMbedTLS::print_mbedtls_error(ret); _cleanup(); diff --git a/modules/mono/README.md b/modules/mono/README.md index 366777cfc1..74b4531dfb 100644 --- a/modules/mono/README.md +++ b/modules/mono/README.md @@ -46,10 +46,10 @@ C# solutions during development to avoid mistakes. # Double Precision Support (REAL_T_IS_DOUBLE) -Follow the above instructions but build Godot with the float=64 argument to scons +Follow the above instructions but build Godot with the precision=double argument to scons -When building the NuGet packages, specify `--float=64` - for example: +When building the NuGet packages, specify `--precision=double` - for example: ```sh ./modules/mono/build_scripts/build_assemblies.py --godot-output-dir ./bin \ - --push-nupkgs-local ~/MyLocalNugetSource --float=64 + --push-nupkgs-local ~/MyLocalNugetSource --precision=double ``` diff --git a/modules/mono/build_scripts/build_assemblies.py b/modules/mono/build_scripts/build_assemblies.py index 7343af0b39..0b91cda9b8 100755 --- a/modules/mono/build_scripts/build_assemblies.py +++ b/modules/mono/build_scripts/build_assemblies.py @@ -193,7 +193,7 @@ def run_msbuild(tools: ToolsLocation, sln: str, msbuild_args: Optional[List[str] return subprocess.call(args, env=msbuild_env) -def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, float_size): +def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision): target_filenames = [ "GodotSharp.dll", "GodotSharp.pdb", @@ -214,7 +214,7 @@ def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, flo args = ["/restore", "/t:Build", "/p:Configuration=" + build_config, "/p:NoWarn=1591"] if push_nupkgs_local: args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local] - if float_size == "64": + if precision == "double": args += ["/p:GodotFloat64=true"] sln = os.path.join(module_dir, "glue/GodotSharp/GodotSharp.sln") @@ -303,12 +303,12 @@ def generate_sdk_package_versions(): f.close() -def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local, float_size): +def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local, precision): # Generate SdkPackageVersions.props generate_sdk_package_versions() # Godot API - exit_code = build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, float_size) + exit_code = build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision) if exit_code != 0: return exit_code @@ -319,7 +319,7 @@ def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, p ) if push_nupkgs_local: args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local] - if float_size == "64": + if precision == "double": args += ["/p:GodotFloat64=true"] exit_code = run_msbuild(msbuild_tool, sln=sln, msbuild_args=args) if exit_code != 0: @@ -329,7 +329,7 @@ def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, p args = ["/restore", "/t:Build", "/p:Configuration=Release"] if push_nupkgs_local: args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local] - if float_size == "64": + if precision == "double": args += ["/p:GodotFloat64=true"] sln = os.path.join(module_dir, "editor/Godot.NET.Sdk/Godot.NET.Sdk.sln") exit_code = run_msbuild(msbuild_tool, sln=sln, msbuild_args=args) @@ -354,7 +354,9 @@ def main(): parser.add_argument("--godot-platform", type=str, default="") parser.add_argument("--mono-prefix", type=str, default="") parser.add_argument("--push-nupkgs-local", type=str, default="") - parser.add_argument("--float", type=str, default="32", choices=["32", "64"], help="Floating-point precision") + parser.add_argument( + "--precision", type=str, default="single", choices=["single", "double"], help="Floating-point precision level" + ) args = parser.parse_args() @@ -378,7 +380,7 @@ def main(): args.godot_platform, args.dev_debug, push_nupkgs_local, - args.float, + args.precision, ) sys.exit(exit_code) diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 345d2e4694..137fd61a25 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -46,6 +46,7 @@ #include "editor/editor_internal_calls.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" +#include "editor/inspector_dock.h" #include "editor/node_dock.h" #include "editor/script_templates/templates.gen.h" #endif @@ -390,10 +391,10 @@ bool CSharpLanguage::supports_builtin_mode() const { #ifdef TOOLS_ENABLED static String variant_type_to_managed_name(const String &p_var_type_name) { if (p_var_type_name.is_empty()) { - return "object"; + return "Variant"; } - if (!ClassDB::class_exists(p_var_type_name)) { + if (ClassDB::class_exists(p_var_type_name)) { return p_var_type_name; } @@ -401,12 +402,12 @@ static String variant_type_to_managed_name(const String &p_var_type_name) { return "Godot.Object"; } + if (p_var_type_name == Variant::get_type_name(Variant::INT)) { + return "long"; + } + if (p_var_type_name == Variant::get_type_name(Variant::FLOAT)) { -#ifdef REAL_T_IS_DOUBLE return "double"; -#else - return "float"; -#endif } if (p_var_type_name == Variant::get_type_name(Variant::STRING)) { @@ -484,7 +485,7 @@ static String variant_type_to_managed_name(const String &p_var_type_name) { } } - return "object"; + return "Variant"; } String CSharpLanguage::make_function(const String &, const String &p_name, const PackedStringArray &p_args) const { @@ -710,6 +711,12 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { return; } + if (!Engine::get_singleton()->is_editor_hint()) { + // We disable collectible assemblies in the game player, because the limitations cause + // issues with mocking libraries. As such, we can only reload assemblies in the editor. + return; + } + // TODO: // Currently, this reloads all scripts, including those whose class is not part of the // assembly load context being unloaded. As such, we unnecessarily reload GodotTools. diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props index 0459257106..0d0889c491 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props @@ -12,8 +12,7 @@ <Configurations>Debug;ExportDebug;ExportRelease</Configurations> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> - <GodotProjectDir Condition=" '$(SolutionDir)' != '' ">$(SolutionDir)</GodotProjectDir> - <GodotProjectDir Condition=" '$(SolutionDir)' == '' ">$(MSBuildProjectDirectory)</GodotProjectDir> + <GodotProjectDir Condition=" '$(GodotProjectDir)' == '' ">$(MSBuildProjectDirectory)</GodotProjectDir> <GodotProjectDir>$([MSBuild]::EnsureTrailingSlash('$(GodotProjectDir)'))</GodotProjectDir> <!-- Custom output paths for Godot projects. In brief, 'bin\' and 'obj\' are moved to '$(GodotProjectDir)\.godot\mono\temp\'. --> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs index ac8d6473a6..9a46b7d164 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; #pragma warning disable CS0169 @@ -83,6 +84,10 @@ namespace Godot.SourceGenerators.Sample [Export] private StringName[] field_StringNameArray = { "foo", "bar" }; [Export] private NodePath[] field_NodePathArray = { "foo", "bar" }; [Export] private RID[] field_RIDArray = { default, default, default }; + // Note we use Array and not System.Array. This tests the generated namespace qualification. + [Export] private Int32[] field_empty_Int32Array = Array.Empty<Int32>(); + // Note we use List and not System.Collections.Generic. + [Export] private int[] field_array_from_list = new List<int>(Array.Empty<int>()).ToArray(); // Variant [Export] private Variant field_Variant = "foo"; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs index 3020cfbc50..eb83833b40 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs @@ -12,6 +12,95 @@ namespace Godot.SourceGenerators.Sample [SuppressMessage("ReSharper", "InconsistentNaming")] public partial class ExportedProperties : Godot.Object { + // Do not generate default value + private String _notGenerate_Property_String = new string("not generate"); + [Export] + public String NotGenerate_Complex_Lamda_Property + { + get => _notGenerate_Property_String + Convert.ToInt32("1"); + set => _notGenerate_Property_String = value; + } + + [Export] + public String NotGenerate_Lamda_NoField_Property + { + get => new string("not generate"); + set => _notGenerate_Property_String = value; + } + + [Export] + public String NotGenerate_Complex_Return_Property + { + get + { + return _notGenerate_Property_String + Convert.ToInt32("1"); + } + set + { + _notGenerate_Property_String = value; + } + } + + private int _notGenerate_Property_Int = 1; + [Export] + public string NotGenerate_Returns_Property + { + get + { + if (_notGenerate_Property_Int == 1) + { + return "a"; + } + else + { + return "b"; + } + } + set + { + _notGenerate_Property_Int = value == "a" ? 1 : 2; + } + } + + // Full Property + private String _fullProperty_String = "FullProperty_String"; + [Export] + public String FullProperty_String + { + get + { + return _fullProperty_String; + } + set + { + _fullProperty_String = value; + } + } + + private String _fullProperty_String_Complex = new string("FullProperty_String_Complex") + Convert.ToInt32("1"); + [Export] + public String FullProperty_String_Complex + { + get + { + return _fullProperty_String_Complex; + } + set + { + _fullProperty_String_Complex = value; + } + } + + // Lamda Property + private String _lamdaProperty_String = "LamdaProperty_String"; + [Export] + public String LamdaProperty_String + { + get => _lamdaProperty_String; + set => _lamdaProperty_String = value; + } + + // Auto Property [Export] private Boolean property_Boolean { get; set; } = true; [Export] private Char property_Char { get; set; } = 'f'; [Export] private SByte property_SByte { get; set; } = 10; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MoreExportedFields.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MoreExportedFields.cs new file mode 100644 index 0000000000..a6c8e52667 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/MoreExportedFields.cs @@ -0,0 +1,19 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +#pragma warning disable CS0169 +#pragma warning disable CS0414 + +namespace Godot.SourceGenerators.Sample +{ + [SuppressMessage("ReSharper", "BuiltInTypeReferenceStyle")] + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + [SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeEvident")] + [SuppressMessage("ReSharper", "InconsistentNaming")] + // We split the definition of ExportedFields to verify properties work across multiple files. + public partial class ExportedFields : Godot.Object + { + // Note we use Array and not System.Array. This tests the generated namespace qualification. + [Export] private Int64[] field_empty_Int64Array = Array.Empty<Int64>(); + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs index e28788ec0b..4eed2d7b7b 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs @@ -14,7 +14,7 @@ namespace Godot.SourceGenerators { string message = "Missing partial modifier on declaration of type '" + - $"{symbol.FullQualifiedName()}' which is a subclass of '{GodotClasses.Object}'"; + $"{symbol.FullQualifiedNameOmitGlobal()}' which is a subclass of '{GodotClasses.Object}'"; string description = $"{message}. Subclasses of '{GodotClasses.Object}' " + "must be declared with the partial modifier."; @@ -41,7 +41,7 @@ namespace Godot.SourceGenerators .GetDeclaredSymbol(outerTypeDeclSyntax); string fullQualifiedName = outerSymbol is INamedTypeSymbol namedTypeSymbol ? - namedTypeSymbol.FullQualifiedName() : + namedTypeSymbol.FullQualifiedNameOmitGlobal() : "type not found"; string message = diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs index 8de12de23b..d67e57edc2 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -148,22 +149,73 @@ namespace Godot.SourceGenerators }; } + public static string NameWithTypeParameters(this INamedTypeSymbol symbol) + { + return symbol.IsGenericType ? + string.Concat(symbol.Name, "<", string.Join(", ", symbol.TypeParameters), ">") : + symbol.Name; + } + private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } = SymbolDisplayFormat.FullyQualifiedFormat .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); - public static string FullQualifiedName(this ITypeSymbol symbol) + private static SymbolDisplayFormat FullyQualifiedFormatIncludeGlobal { get; } = + SymbolDisplayFormat.FullyQualifiedFormat + .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Included); + + public static string FullQualifiedNameOmitGlobal(this ITypeSymbol symbol) => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal); - public static string NameWithTypeParameters(this INamedTypeSymbol symbol) + public static string FullQualifiedNameOmitGlobal(this INamespaceSymbol namespaceSymbol) + => namespaceSymbol.ToDisplayString(FullyQualifiedFormatOmitGlobal); + + public static string FullQualifiedNameIncludeGlobal(this ITypeSymbol symbol) + => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatIncludeGlobal); + + public static string FullQualifiedNameIncludeGlobal(this INamespaceSymbol namespaceSymbol) + => namespaceSymbol.ToDisplayString(FullyQualifiedFormatIncludeGlobal); + + public static string FullQualifiedSyntax(this SyntaxNode node, SemanticModel sm) { - return symbol.IsGenericType ? - string.Concat(symbol.Name, "<", string.Join(", ", symbol.TypeParameters), ">") : - symbol.Name; + StringBuilder sb = new(); + FullQualifiedSyntax(node, sm, sb, true); + return sb.ToString(); } - public static string FullQualifiedName(this INamespaceSymbol namespaceSymbol) - => namespaceSymbol.ToDisplayString(FullyQualifiedFormatOmitGlobal); + private static void FullQualifiedSyntax(SyntaxNode node, SemanticModel sm, StringBuilder sb, bool isFirstNode) + { + if (node is NameSyntax ns && isFirstNode) + { + SymbolInfo nameInfo = sm.GetSymbolInfo(ns); + sb.Append(nameInfo.Symbol?.ToDisplayString(FullyQualifiedFormatIncludeGlobal) ?? ns.ToString()); + return; + } + + bool innerIsFirstNode = true; + foreach (var child in node.ChildNodesAndTokens()) + { + if (child.HasLeadingTrivia) + { + sb.Append(child.GetLeadingTrivia()); + } + + if (child.IsNode) + { + FullQualifiedSyntax(child.AsNode()!, sm, sb, isFirstNode: innerIsFirstNode); + innerIsFirstNode = false; + } + else + { + sb.Append(child); + } + + if (child.HasTrailingTrivia) + { + sb.Append(child.GetTrailingTrivia()); + } + } + } public static string SanitizeQualifiedNameForUniqueHint(this string qualifiedName) => qualifiedName @@ -216,8 +268,9 @@ namespace Godot.SourceGenerators if (parameters.Length > paramTypes.Length) return null; // Ignore incompatible method - return new GodotMethodData(method, paramTypes, parameters - .Select(p => p.Type).ToImmutableArray(), retType, retSymbol); + return new GodotMethodData(method, paramTypes, + parameters.Select(p => p.Type).ToImmutableArray(), + retType != null ? (retType.Value, retSymbol) : null); } public static IEnumerable<GodotMethodData> WhereHasGodotCompatibleSignature( @@ -278,10 +331,10 @@ namespace Godot.SourceGenerators public static string Path(this Location location) => location.SourceTree?.GetLineSpan(location.SourceSpan).Path - ?? location.GetLineSpan().Path; + ?? location.GetLineSpan().Path; public static int StartLine(this Location location) => location.SourceTree?.GetLineSpan(location.SourceSpan).StartLinePosition.Line - ?? location.GetLineSpan().StartLinePosition.Line; + ?? location.GetLineSpan().StartLinePosition.Line; } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs index 1a25d684a0..88c0e71155 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs @@ -71,29 +71,29 @@ namespace Godot.SourceGenerators Expression = 19, PlaceholderText = 20, ColorNoAlpha = 21, - ImageCompressLossy = 22, - ImageCompressLossless = 23, - ObjectId = 24, - TypeString = 25, - NodePathToEditedNode = 26, - MethodOfVariantType = 27, - MethodOfBaseType = 28, - MethodOfInstance = 29, - MethodOfScript = 30, - PropertyOfVariantType = 31, - PropertyOfBaseType = 32, - PropertyOfInstance = 33, - PropertyOfScript = 34, - ObjectTooBig = 35, - NodePathValidTypes = 36, - SaveFile = 37, - GlobalSaveFile = 38, - IntIsObjectid = 39, - IntIsPointer = 41, - ArrayType = 40, - LocaleId = 42, - LocalizableString = 43, - NodeType = 44, + ObjectId = 22, + TypeString = 23, + NodePathToEditedNode = 24, + MethodOfVariantType = 25, + MethodOfBaseType = 26, + MethodOfInstance = 27, + MethodOfScript = 28, + PropertyOfVariantType = 29, + PropertyOfBaseType = 30, + PropertyOfInstance = 31, + PropertyOfScript = 32, + ObjectTooBig = 33, + NodePathValidTypes = 34, + SaveFile = 35, + GlobalSaveFile = 36, + IntIsObjectid = 37, + IntIsPointer = 38, + ArrayType = 39, + LocaleId = 40, + LocalizableString = 41, + NodeType = 42, + HideQuaternionEdit = 43, + Password = 44, Max = 45 } @@ -128,12 +128,14 @@ namespace Godot.SourceGenerators DeferredSetResource = 33554432, EditorInstantiateObject = 67108864, EditorBasicSetting = 134217728, + ReadOnly = 268435456, Array = 536870912, Default = 6, DefaultIntl = 38, NoEditor = 2 } + [Flags] public enum MethodFlags { Normal = 1, diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs index db395e21cb..0760ea11bb 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs @@ -3,26 +3,24 @@ using Microsoft.CodeAnalysis; namespace Godot.SourceGenerators { - public struct GodotMethodData + public readonly struct GodotMethodData { public GodotMethodData(IMethodSymbol method, ImmutableArray<MarshalType> paramTypes, - ImmutableArray<ITypeSymbol> paramTypeSymbols, MarshalType? retType, ITypeSymbol? retSymbol) + ImmutableArray<ITypeSymbol> paramTypeSymbols, (MarshalType MarshalType, ITypeSymbol TypeSymbol)? retType) { Method = method; ParamTypes = paramTypes; ParamTypeSymbols = paramTypeSymbols; RetType = retType; - RetSymbol = retSymbol; } public IMethodSymbol Method { get; } public ImmutableArray<MarshalType> ParamTypes { get; } public ImmutableArray<ITypeSymbol> ParamTypeSymbols { get; } - public MarshalType? RetType { get; } - public ITypeSymbol? RetSymbol { get; } + public (MarshalType MarshalType, ITypeSymbol TypeSymbol)? RetType { get; } } - public struct GodotSignalDelegateData + public readonly struct GodotSignalDelegateData { public GodotSignalDelegateData(string name, INamedTypeSymbol delegateSymbol, GodotMethodData invokeMethodData) { @@ -36,7 +34,7 @@ namespace Godot.SourceGenerators public GodotMethodData InvokeMethodData { get; } } - public struct GodotPropertyData + public readonly struct GodotPropertyData { public GodotPropertyData(IPropertySymbol propertySymbol, MarshalType type) { @@ -48,7 +46,7 @@ namespace Godot.SourceGenerators public MarshalType Type { get; } } - public struct GodotFieldData + public readonly struct GodotFieldData { public GodotFieldData(IFieldSymbol fieldSymbol, MarshalType type) { diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs index bd40675fd3..6dac120d15 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs @@ -220,7 +220,7 @@ namespace Godot.SourceGenerators _ => null }; case "Collections" - when type.ContainingNamespace?.FullQualifiedName() == "Godot.Collections": + when type.ContainingNamespace?.FullQualifiedNameOmitGlobal() == "Godot.Collections": return type switch { { Name: "Dictionary" } => @@ -304,240 +304,41 @@ namespace Godot.SourceGenerators { return marshalType switch { - MarshalType.Boolean => - source.Append(VariantUtils, ".ConvertToBool(", inputExpr, ")"), - MarshalType.Char => - source.Append("(char)", VariantUtils, ".ConvertToUInt16(", inputExpr, ")"), - MarshalType.SByte => - source.Append(VariantUtils, ".ConvertToInt8(", inputExpr, ")"), - MarshalType.Int16 => - source.Append(VariantUtils, ".ConvertToInt16(", inputExpr, ")"), - MarshalType.Int32 => - source.Append(VariantUtils, ".ConvertToInt32(", inputExpr, ")"), - MarshalType.Int64 => - source.Append(VariantUtils, ".ConvertToInt64(", inputExpr, ")"), - MarshalType.Byte => - source.Append(VariantUtils, ".ConvertToUInt8(", inputExpr, ")"), - MarshalType.UInt16 => - source.Append(VariantUtils, ".ConvertToUInt16(", inputExpr, ")"), - MarshalType.UInt32 => - source.Append(VariantUtils, ".ConvertToUInt32(", inputExpr, ")"), - MarshalType.UInt64 => - source.Append(VariantUtils, ".ConvertToUInt64(", inputExpr, ")"), - MarshalType.Single => - source.Append(VariantUtils, ".ConvertToFloat32(", inputExpr, ")"), - MarshalType.Double => - source.Append(VariantUtils, ".ConvertToFloat64(", inputExpr, ")"), - MarshalType.String => - source.Append(VariantUtils, ".ConvertToStringObject(", inputExpr, ")"), - MarshalType.Vector2 => - source.Append(VariantUtils, ".ConvertToVector2(", inputExpr, ")"), - MarshalType.Vector2i => - source.Append(VariantUtils, ".ConvertToVector2i(", inputExpr, ")"), - MarshalType.Rect2 => - source.Append(VariantUtils, ".ConvertToRect2(", inputExpr, ")"), - MarshalType.Rect2i => - source.Append(VariantUtils, ".ConvertToRect2i(", inputExpr, ")"), - MarshalType.Transform2D => - source.Append(VariantUtils, ".ConvertToTransform2D(", inputExpr, ")"), - MarshalType.Vector3 => - source.Append(VariantUtils, ".ConvertToVector3(", inputExpr, ")"), - MarshalType.Vector3i => - source.Append(VariantUtils, ".ConvertToVector3i(", inputExpr, ")"), - MarshalType.Basis => - source.Append(VariantUtils, ".ConvertToBasis(", inputExpr, ")"), - MarshalType.Quaternion => - source.Append(VariantUtils, ".ConvertToQuaternion(", inputExpr, ")"), - MarshalType.Transform3D => - source.Append(VariantUtils, ".ConvertToTransform3D(", inputExpr, ")"), - MarshalType.Vector4 => - source.Append(VariantUtils, ".ConvertToVector4(", inputExpr, ")"), - MarshalType.Vector4i => - source.Append(VariantUtils, ".ConvertToVector4i(", inputExpr, ")"), - MarshalType.Projection => - source.Append(VariantUtils, ".ConvertToProjection(", inputExpr, ")"), - MarshalType.AABB => - source.Append(VariantUtils, ".ConvertToAABB(", inputExpr, ")"), - MarshalType.Color => - source.Append(VariantUtils, ".ConvertToColor(", inputExpr, ")"), - MarshalType.Plane => - source.Append(VariantUtils, ".ConvertToPlane(", inputExpr, ")"), - MarshalType.Callable => - source.Append(VariantUtils, ".ConvertToCallableManaged(", inputExpr, ")"), - MarshalType.SignalInfo => - source.Append(VariantUtils, ".ConvertToSignalInfo(", inputExpr, ")"), - MarshalType.Enum => - source.Append("(", typeSymbol.FullQualifiedName(), - ")", VariantUtils, ".ConvertToInt32(", inputExpr, ")"), - MarshalType.ByteArray => - source.Append(VariantUtils, ".ConvertAsPackedByteArrayToSystemArray(", inputExpr, ")"), - MarshalType.Int32Array => - source.Append(VariantUtils, ".ConvertAsPackedInt32ArrayToSystemArray(", inputExpr, ")"), - MarshalType.Int64Array => - source.Append(VariantUtils, ".ConvertAsPackedInt64ArrayToSystemArray(", inputExpr, ")"), - MarshalType.Float32Array => - source.Append(VariantUtils, ".ConvertAsPackedFloat32ArrayToSystemArray(", inputExpr, ")"), - MarshalType.Float64Array => - source.Append(VariantUtils, ".ConvertAsPackedFloat64ArrayToSystemArray(", inputExpr, ")"), - MarshalType.StringArray => - source.Append(VariantUtils, ".ConvertAsPackedStringArrayToSystemArray(", inputExpr, ")"), - MarshalType.Vector2Array => - source.Append(VariantUtils, ".ConvertAsPackedVector2ArrayToSystemArray(", inputExpr, ")"), - MarshalType.Vector3Array => - source.Append(VariantUtils, ".ConvertAsPackedVector3ArrayToSystemArray(", inputExpr, ")"), - MarshalType.ColorArray => - source.Append(VariantUtils, ".ConvertAsPackedColorArrayToSystemArray(", inputExpr, ")"), + // We need a special case for GodotObjectOrDerived[], because it's not supported by VariantUtils.ConvertTo<T> MarshalType.GodotObjectOrDerivedArray => source.Append(VariantUtils, ".ConvertToSystemArrayOfGodotObject<", - ((IArrayTypeSymbol)typeSymbol).ElementType.FullQualifiedName(), ">(", inputExpr, ")"), - MarshalType.SystemArrayOfStringName => - source.Append(VariantUtils, ".ConvertToSystemArrayOfStringName(", inputExpr, ")"), - MarshalType.SystemArrayOfNodePath => - source.Append(VariantUtils, ".ConvertToSystemArrayOfNodePath(", inputExpr, ")"), - MarshalType.SystemArrayOfRID => - source.Append(VariantUtils, ".ConvertToSystemArrayOfRID(", inputExpr, ")"), - MarshalType.Variant => - source.Append("global::Godot.Variant.CreateCopyingBorrowed(", inputExpr, ")"), - MarshalType.GodotObjectOrDerived => - source.Append("(", typeSymbol.FullQualifiedName(), - ")", VariantUtils, ".ConvertToGodotObject(", inputExpr, ")"), - MarshalType.StringName => - source.Append(VariantUtils, ".ConvertToStringNameObject(", inputExpr, ")"), - MarshalType.NodePath => - source.Append(VariantUtils, ".ConvertToNodePathObject(", inputExpr, ")"), - MarshalType.RID => - source.Append(VariantUtils, ".ConvertToRID(", inputExpr, ")"), - MarshalType.GodotDictionary => - source.Append(VariantUtils, ".ConvertToDictionaryObject(", inputExpr, ")"), - MarshalType.GodotArray => - source.Append(VariantUtils, ".ConvertToArrayObject(", inputExpr, ")"), + ((IArrayTypeSymbol)typeSymbol).ElementType.FullQualifiedNameIncludeGlobal(), ">(", + inputExpr, ")"), + // We need a special case for generic Godot collections and GodotObjectOrDerived[], because VariantUtils.ConvertTo<T> is slower MarshalType.GodotGenericDictionary => source.Append(VariantUtils, ".ConvertToDictionaryObject<", - ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ", ", - ((INamedTypeSymbol)typeSymbol).TypeArguments[1].FullQualifiedName(), ">(", inputExpr, ")"), + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedNameIncludeGlobal(), ", ", + ((INamedTypeSymbol)typeSymbol).TypeArguments[1].FullQualifiedNameIncludeGlobal(), ">(", + inputExpr, ")"), MarshalType.GodotGenericArray => source.Append(VariantUtils, ".ConvertToArrayObject<", - ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ">(", inputExpr, ")"), - _ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType, - "Received unexpected marshal type") + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedNameIncludeGlobal(), ">(", + inputExpr, ")"), + _ => source.Append(VariantUtils, ".ConvertTo<", + typeSymbol.FullQualifiedNameIncludeGlobal(), ">(", inputExpr, ")"), }; } - public static StringBuilder AppendManagedToNativeVariantExpr( - this StringBuilder source, string inputExpr, MarshalType marshalType) + public static StringBuilder AppendManagedToNativeVariantExpr(this StringBuilder source, + string inputExpr, ITypeSymbol typeSymbol, MarshalType marshalType) { return marshalType switch { - MarshalType.Boolean => - source.Append(VariantUtils, ".CreateFromBool(", inputExpr, ")"), - MarshalType.Char => - source.Append(VariantUtils, ".CreateFromInt((ushort)", inputExpr, ")"), - MarshalType.SByte => - source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), - MarshalType.Int16 => - source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), - MarshalType.Int32 => - source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), - MarshalType.Int64 => - source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), - MarshalType.Byte => - source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), - MarshalType.UInt16 => - source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), - MarshalType.UInt32 => - source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), - MarshalType.UInt64 => - source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), - MarshalType.Single => - source.Append(VariantUtils, ".CreateFromFloat(", inputExpr, ")"), - MarshalType.Double => - source.Append(VariantUtils, ".CreateFromFloat(", inputExpr, ")"), - MarshalType.String => - source.Append(VariantUtils, ".CreateFromString(", inputExpr, ")"), - MarshalType.Vector2 => - source.Append(VariantUtils, ".CreateFromVector2(", inputExpr, ")"), - MarshalType.Vector2i => - source.Append(VariantUtils, ".CreateFromVector2i(", inputExpr, ")"), - MarshalType.Rect2 => - source.Append(VariantUtils, ".CreateFromRect2(", inputExpr, ")"), - MarshalType.Rect2i => - source.Append(VariantUtils, ".CreateFromRect2i(", inputExpr, ")"), - MarshalType.Transform2D => - source.Append(VariantUtils, ".CreateFromTransform2D(", inputExpr, ")"), - MarshalType.Vector3 => - source.Append(VariantUtils, ".CreateFromVector3(", inputExpr, ")"), - MarshalType.Vector3i => - source.Append(VariantUtils, ".CreateFromVector3i(", inputExpr, ")"), - MarshalType.Basis => - source.Append(VariantUtils, ".CreateFromBasis(", inputExpr, ")"), - MarshalType.Quaternion => - source.Append(VariantUtils, ".CreateFromQuaternion(", inputExpr, ")"), - MarshalType.Transform3D => - source.Append(VariantUtils, ".CreateFromTransform3D(", inputExpr, ")"), - MarshalType.Vector4 => - source.Append(VariantUtils, ".CreateFromVector4(", inputExpr, ")"), - MarshalType.Vector4i => - source.Append(VariantUtils, ".CreateFromVector4i(", inputExpr, ")"), - MarshalType.Projection => - source.Append(VariantUtils, ".CreateFromProjection(", inputExpr, ")"), - MarshalType.AABB => - source.Append(VariantUtils, ".CreateFromAABB(", inputExpr, ")"), - MarshalType.Color => - source.Append(VariantUtils, ".CreateFromColor(", inputExpr, ")"), - MarshalType.Plane => - source.Append(VariantUtils, ".CreateFromPlane(", inputExpr, ")"), - MarshalType.Callable => - source.Append(VariantUtils, ".CreateFromCallable(", inputExpr, ")"), - MarshalType.SignalInfo => - source.Append(VariantUtils, ".CreateFromSignalInfo(", inputExpr, ")"), - MarshalType.Enum => - source.Append(VariantUtils, ".CreateFromInt((int)", inputExpr, ")"), - MarshalType.ByteArray => - source.Append(VariantUtils, ".CreateFromPackedByteArray(", inputExpr, ")"), - MarshalType.Int32Array => - source.Append(VariantUtils, ".CreateFromPackedInt32Array(", inputExpr, ")"), - MarshalType.Int64Array => - source.Append(VariantUtils, ".CreateFromPackedInt64Array(", inputExpr, ")"), - MarshalType.Float32Array => - source.Append(VariantUtils, ".CreateFromPackedFloat32Array(", inputExpr, ")"), - MarshalType.Float64Array => - source.Append(VariantUtils, ".CreateFromPackedFloat64Array(", inputExpr, ")"), - MarshalType.StringArray => - source.Append(VariantUtils, ".CreateFromPackedStringArray(", inputExpr, ")"), - MarshalType.Vector2Array => - source.Append(VariantUtils, ".CreateFromPackedVector2Array(", inputExpr, ")"), - MarshalType.Vector3Array => - source.Append(VariantUtils, ".CreateFromPackedVector3Array(", inputExpr, ")"), - MarshalType.ColorArray => - source.Append(VariantUtils, ".CreateFromPackedColorArray(", inputExpr, ")"), + // We need a special case for GodotObjectOrDerived[], because it's not supported by VariantUtils.CreateFrom<T> MarshalType.GodotObjectOrDerivedArray => source.Append(VariantUtils, ".CreateFromSystemArrayOfGodotObject(", inputExpr, ")"), - MarshalType.SystemArrayOfStringName => - source.Append(VariantUtils, ".CreateFromSystemArrayOfStringName(", inputExpr, ")"), - MarshalType.SystemArrayOfNodePath => - source.Append(VariantUtils, ".CreateFromSystemArrayOfNodePath(", inputExpr, ")"), - MarshalType.SystemArrayOfRID => - source.Append(VariantUtils, ".CreateFromSystemArrayOfRID(", inputExpr, ")"), - MarshalType.Variant => - source.Append(inputExpr, ".CopyNativeVariant()"), - MarshalType.GodotObjectOrDerived => - source.Append(VariantUtils, ".CreateFromGodotObject(", inputExpr, ")"), - MarshalType.StringName => - source.Append(VariantUtils, ".CreateFromStringName(", inputExpr, ")"), - MarshalType.NodePath => - source.Append(VariantUtils, ".CreateFromNodePath(", inputExpr, ")"), - MarshalType.RID => - source.Append(VariantUtils, ".CreateFromRID(", inputExpr, ")"), - MarshalType.GodotDictionary => - source.Append(VariantUtils, ".CreateFromDictionary(", inputExpr, ")"), - MarshalType.GodotArray => - source.Append(VariantUtils, ".CreateFromArray(", inputExpr, ")"), + // We need a special case for generic Godot collections and GodotObjectOrDerived[], because VariantUtils.CreateFrom<T> is slower MarshalType.GodotGenericDictionary => source.Append(VariantUtils, ".CreateFromDictionary(", inputExpr, ")"), MarshalType.GodotGenericArray => source.Append(VariantUtils, ".CreateFromArray(", inputExpr, ")"), - _ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType, - "Received unexpected marshal type") + _ => source.Append(VariantUtils, ".CreateFrom<", + typeSymbol.FullQualifiedNameIncludeGlobal(), ">(", inputExpr, ")"), }; } @@ -546,137 +347,37 @@ namespace Godot.SourceGenerators { return marshalType switch { - MarshalType.Boolean => source.Append(inputExpr, ".AsBool()"), - MarshalType.Char => source.Append(inputExpr, ".AsChar()"), - MarshalType.SByte => source.Append(inputExpr, ".AsSByte()"), - MarshalType.Int16 => source.Append(inputExpr, ".AsInt16()"), - MarshalType.Int32 => source.Append(inputExpr, ".AsInt32()"), - MarshalType.Int64 => source.Append(inputExpr, ".AsInt64()"), - MarshalType.Byte => source.Append(inputExpr, ".AsByte()"), - MarshalType.UInt16 => source.Append(inputExpr, ".AsUInt16()"), - MarshalType.UInt32 => source.Append(inputExpr, ".AsUInt32()"), - MarshalType.UInt64 => source.Append(inputExpr, ".AsUInt64()"), - MarshalType.Single => source.Append(inputExpr, ".AsSingle()"), - MarshalType.Double => source.Append(inputExpr, ".AsDouble()"), - MarshalType.String => source.Append(inputExpr, ".AsString()"), - MarshalType.Vector2 => source.Append(inputExpr, ".AsVector2()"), - MarshalType.Vector2i => source.Append(inputExpr, ".AsVector2i()"), - MarshalType.Rect2 => source.Append(inputExpr, ".AsRect2()"), - MarshalType.Rect2i => source.Append(inputExpr, ".AsRect2i()"), - MarshalType.Transform2D => source.Append(inputExpr, ".AsTransform2D()"), - MarshalType.Vector3 => source.Append(inputExpr, ".AsVector3()"), - MarshalType.Vector3i => source.Append(inputExpr, ".AsVector3i()"), - MarshalType.Basis => source.Append(inputExpr, ".AsBasis()"), - MarshalType.Quaternion => source.Append(inputExpr, ".AsQuaternion()"), - MarshalType.Transform3D => source.Append(inputExpr, ".AsTransform3D()"), - MarshalType.Vector4 => source.Append(inputExpr, ".AsVector4()"), - MarshalType.Vector4i => source.Append(inputExpr, ".AsVector4i()"), - MarshalType.Projection => source.Append(inputExpr, ".AsProjection()"), - MarshalType.AABB => source.Append(inputExpr, ".AsAABB()"), - MarshalType.Color => source.Append(inputExpr, ".AsColor()"), - MarshalType.Plane => source.Append(inputExpr, ".AsPlane()"), - MarshalType.Callable => source.Append(inputExpr, ".AsCallable()"), - MarshalType.SignalInfo => source.Append(inputExpr, ".AsSignalInfo()"), - MarshalType.Enum => - source.Append("(", typeSymbol.FullQualifiedName(), ")", inputExpr, ".AsInt64()"), - MarshalType.ByteArray => source.Append(inputExpr, ".AsByteArray()"), - MarshalType.Int32Array => source.Append(inputExpr, ".AsInt32Array()"), - MarshalType.Int64Array => source.Append(inputExpr, ".AsInt64Array()"), - MarshalType.Float32Array => source.Append(inputExpr, ".AsFloat32Array()"), - MarshalType.Float64Array => source.Append(inputExpr, ".AsFloat64Array()"), - MarshalType.StringArray => source.Append(inputExpr, ".AsStringArray()"), - MarshalType.Vector2Array => source.Append(inputExpr, ".AsVector2Array()"), - MarshalType.Vector3Array => source.Append(inputExpr, ".AsVector3Array()"), - MarshalType.ColorArray => source.Append(inputExpr, ".AsColorArray()"), - MarshalType.GodotObjectOrDerivedArray => source.Append(inputExpr, ".AsGodotObjectArray<", - ((IArrayTypeSymbol)typeSymbol).ElementType.FullQualifiedName(), ">()"), - MarshalType.SystemArrayOfStringName => source.Append(inputExpr, ".AsSystemArrayOfStringName()"), - MarshalType.SystemArrayOfNodePath => source.Append(inputExpr, ".AsSystemArrayOfNodePath()"), - MarshalType.SystemArrayOfRID => source.Append(inputExpr, ".AsSystemArrayOfRID()"), - MarshalType.Variant => source.Append(inputExpr), - MarshalType.GodotObjectOrDerived => source.Append("(", - typeSymbol.FullQualifiedName(), ")", inputExpr, ".AsGodotObject()"), - MarshalType.StringName => source.Append(inputExpr, ".AsStringName()"), - MarshalType.NodePath => source.Append(inputExpr, ".AsNodePath()"), - MarshalType.RID => source.Append(inputExpr, ".AsRID()"), - MarshalType.GodotDictionary => source.Append(inputExpr, ".AsGodotDictionary()"), - MarshalType.GodotArray => source.Append(inputExpr, ".AsGodotArray()"), - MarshalType.GodotGenericDictionary => source.Append(inputExpr, ".AsGodotDictionary<", - ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ", ", - ((INamedTypeSymbol)typeSymbol).TypeArguments[1].FullQualifiedName(), ">()"), - MarshalType.GodotGenericArray => source.Append(inputExpr, ".AsGodotArray<", - ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ">()"), - _ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType, - "Received unexpected marshal type") + // We need a special case for GodotObjectOrDerived[], because it's not supported by Variant.As<T> + MarshalType.GodotObjectOrDerivedArray => + source.Append(inputExpr, ".AsGodotObjectArray<", + ((IArrayTypeSymbol)typeSymbol).ElementType.FullQualifiedNameIncludeGlobal(), ">()"), + // We need a special case for generic Godot collections and GodotObjectOrDerived[], because Variant.As<T> is slower + MarshalType.GodotGenericDictionary => + source.Append(inputExpr, ".AsGodotDictionary<", + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedNameIncludeGlobal(), ", ", + ((INamedTypeSymbol)typeSymbol).TypeArguments[1].FullQualifiedNameIncludeGlobal(), ">()"), + MarshalType.GodotGenericArray => + source.Append(inputExpr, ".AsGodotArray<", + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedNameIncludeGlobal(), ">()"), + _ => source.Append(inputExpr, ".As<", + typeSymbol.FullQualifiedNameIncludeGlobal(), ">()") }; } public static StringBuilder AppendManagedToVariantExpr(this StringBuilder source, - string inputExpr, MarshalType marshalType) + string inputExpr, ITypeSymbol typeSymbol, MarshalType marshalType) { - switch (marshalType) + return marshalType switch { - case MarshalType.Boolean: - case MarshalType.Char: - case MarshalType.SByte: - case MarshalType.Int16: - case MarshalType.Int32: - case MarshalType.Int64: - case MarshalType.Byte: - case MarshalType.UInt16: - case MarshalType.UInt32: - case MarshalType.UInt64: - case MarshalType.Single: - case MarshalType.Double: - case MarshalType.String: - case MarshalType.Vector2: - case MarshalType.Vector2i: - case MarshalType.Rect2: - case MarshalType.Rect2i: - case MarshalType.Transform2D: - case MarshalType.Vector3: - case MarshalType.Vector3i: - case MarshalType.Basis: - case MarshalType.Quaternion: - case MarshalType.Transform3D: - case MarshalType.Vector4: - case MarshalType.Vector4i: - case MarshalType.Projection: - case MarshalType.AABB: - case MarshalType.Color: - case MarshalType.Plane: - case MarshalType.Callable: - case MarshalType.SignalInfo: - case MarshalType.ByteArray: - case MarshalType.Int32Array: - case MarshalType.Int64Array: - case MarshalType.Float32Array: - case MarshalType.Float64Array: - case MarshalType.StringArray: - case MarshalType.Vector2Array: - case MarshalType.Vector3Array: - case MarshalType.ColorArray: - case MarshalType.GodotObjectOrDerivedArray: - case MarshalType.SystemArrayOfStringName: - case MarshalType.SystemArrayOfNodePath: - case MarshalType.SystemArrayOfRID: - case MarshalType.GodotObjectOrDerived: - case MarshalType.StringName: - case MarshalType.NodePath: - case MarshalType.RID: - case MarshalType.GodotDictionary: - case MarshalType.GodotArray: - case MarshalType.GodotGenericDictionary: - case MarshalType.GodotGenericArray: - return source.Append("Variant.CreateFrom(", inputExpr, ")"); - case MarshalType.Enum: - return source.Append("Variant.CreateFrom((long)", inputExpr, ")"); - case MarshalType.Variant: - return source.Append(inputExpr); - default: - throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType, - "Received unexpected marshal type"); - } + // We need a special case for GodotObjectOrDerived[], because it's not supported by Variant.From<T> + MarshalType.GodotObjectOrDerivedArray => + source.Append("global::Godot.Variant.CreateFrom(", inputExpr, ")"), + // We need a special case for generic Godot collections, because Variant.From<T> is slower + MarshalType.GodotGenericDictionary or MarshalType.GodotGenericArray => + source.Append("global::Godot.Variant.CreateFrom(", inputExpr, ")"), + _ => source.Append("global::Godot.Variant.From<", + typeSymbol.FullQualifiedNameIncludeGlobal(), ">(", inputExpr, ")") + }; } } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MethodInfo.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MethodInfo.cs index 81c6f2b7d5..bb9be862c4 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MethodInfo.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MethodInfo.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; namespace Godot.SourceGenerators { - internal struct MethodInfo + internal readonly struct MethodInfo { public MethodInfo(string name, PropertyInfo returnVal, MethodFlags flags, List<PropertyInfo>? arguments, diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/PropertyInfo.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/PropertyInfo.cs index b345f5f84d..2b89633ef6 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/PropertyInfo.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/PropertyInfo.cs @@ -1,6 +1,6 @@ namespace Godot.SourceGenerators { - internal struct PropertyInfo + internal readonly struct PropertyInfo { public PropertyInfo(VariantType type, string name, PropertyHint hint, string? hintString, PropertyUsageFlags usage, bool exported) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs index d5d80df643..f79909589e 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs @@ -80,13 +80,13 @@ namespace Godot.SourceGenerators { INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? - namespaceSymbol.FullQualifiedName() : + namespaceSymbol.FullQualifiedNameOmitGlobal() : string.Empty; bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; - string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + string uniqueHint = symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint() + "_ScriptMethods.generated"; var source = new StringBuilder(); @@ -135,7 +135,8 @@ namespace Godot.SourceGenerators source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); - source.Append($" public new class MethodName : {symbol.BaseType.FullQualifiedName()}.MethodName {{\n"); + source.Append( + $" public new class MethodName : {symbol.BaseType.FullQualifiedNameIncludeGlobal()}.MethodName {{\n"); // Generate cached StringNames for methods and properties, for fast lookup @@ -146,7 +147,7 @@ namespace Godot.SourceGenerators foreach (string methodName in distinctMethodNames) { - source.Append(" public new static readonly StringName "); + source.Append(" public new static readonly global::Godot.StringName "); source.Append(methodName); source.Append(" = \""); source.Append(methodName); @@ -159,7 +160,7 @@ namespace Godot.SourceGenerators if (godotClassMethods.Length > 0) { - const string listType = "System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>"; + const string listType = "global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>"; source.Append(" internal new static ") .Append(listType) @@ -248,7 +249,7 @@ namespace Godot.SourceGenerators AppendPropertyInfo(source, methodInfo.ReturnVal); - source.Append(", flags: (Godot.MethodFlags)") + source.Append(", flags: (global::Godot.MethodFlags)") .Append((int)methodInfo.Flags) .Append(", arguments: "); @@ -276,15 +277,15 @@ namespace Godot.SourceGenerators private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo) { - source.Append("new(type: (Godot.Variant.Type)") + source.Append("new(type: (global::Godot.Variant.Type)") .Append((int)propertyInfo.Type) .Append(", name: \"") .Append(propertyInfo.Name) - .Append("\", hint: (Godot.PropertyHint)") + .Append("\", hint: (global::Godot.PropertyHint)") .Append((int)propertyInfo.Hint) .Append(", hintString: \"") .Append(propertyInfo.HintString) - .Append("\", usage: (Godot.PropertyUsageFlags)") + .Append("\", usage: (global::Godot.PropertyUsageFlags)") .Append((int)propertyInfo.Usage) .Append(", exported: ") .Append(propertyInfo.Exported ? "true" : "false") @@ -297,7 +298,7 @@ namespace Godot.SourceGenerators if (method.RetType != null) { - returnVal = DeterminePropertyInfo(method.RetType.Value, name: string.Empty); + returnVal = DeterminePropertyInfo(method.RetType.Value.MarshalType, name: string.Empty); } else { @@ -391,7 +392,8 @@ namespace Godot.SourceGenerators { source.Append(" ret = "); - source.AppendManagedToNativeVariantExpr("callRet", method.RetType.Value); + source.AppendManagedToNativeVariantExpr("callRet", + method.RetType.Value.TypeSymbol, method.RetType.Value.MarshalType); source.Append(";\n"); source.Append(" return true;\n"); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs index ccfb405d26..fb32f6192f 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs @@ -92,11 +92,11 @@ namespace Godot.SourceGenerators INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? - namespaceSymbol.FullQualifiedName() : + namespaceSymbol.FullQualifiedNameOmitGlobal() : string.Empty; bool hasNamespace = classNs.Length != 0; - string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + string uniqueHint = symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint() + "_ScriptPath.generated"; var source = new StringBuilder(); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index 1198c633d9..6c4206a575 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -66,13 +66,13 @@ namespace Godot.SourceGenerators { INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? - namespaceSymbol.FullQualifiedName() : + namespaceSymbol.FullQualifiedNameOmitGlobal() : string.Empty; bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; - string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + string uniqueHint = symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint() + "_ScriptProperties.generated"; var source = new StringBuilder(); @@ -124,14 +124,15 @@ namespace Godot.SourceGenerators source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); - source.Append($" public new class PropertyName : {symbol.BaseType.FullQualifiedName()}.PropertyName {{\n"); + source.Append( + $" public new class PropertyName : {symbol.BaseType.FullQualifiedNameIncludeGlobal()}.PropertyName {{\n"); // Generate cached StringNames for methods and properties, for fast lookup foreach (var property in godotClassProperties) { string propertyName = property.PropertySymbol.Name; - source.Append(" public new static readonly StringName "); + source.Append(" public new static readonly global::Godot.StringName "); source.Append(propertyName); source.Append(" = \""); source.Append(propertyName); @@ -141,7 +142,7 @@ namespace Godot.SourceGenerators foreach (var field in godotClassFields) { string fieldName = field.FieldSymbol.Name; - source.Append(" public new static readonly StringName "); + source.Append(" public new static readonly global::Godot.StringName "); source.Append(fieldName); source.Append(" = \""); source.Append(fieldName); @@ -199,14 +200,14 @@ namespace Godot.SourceGenerators foreach (var property in godotClassProperties) { GeneratePropertyGetter(property.PropertySymbol.Name, - property.Type, source, isFirstEntry); + property.PropertySymbol.Type, property.Type, source, isFirstEntry); isFirstEntry = false; } foreach (var field in godotClassFields) { GeneratePropertyGetter(field.FieldSymbol.Name, - field.Type, source, isFirstEntry); + field.FieldSymbol.Type, field.Type, source, isFirstEntry); isFirstEntry = false; } @@ -216,7 +217,7 @@ namespace Godot.SourceGenerators // Generate GetGodotPropertyList - string dictionaryType = "System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>"; + string dictionaryType = "global::System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>"; source.Append(" internal new static ") .Append(dictionaryType) @@ -292,7 +293,7 @@ namespace Godot.SourceGenerators source.Append("if (name == PropertyName.") .Append(propertyMemberName) .Append(") {\n") - .Append(" ") + .Append(" this.") .Append(propertyMemberName) .Append(" = ") .AppendNativeVariantToManagedExpr("value", propertyTypeSymbol, propertyMarshalType) @@ -303,6 +304,7 @@ namespace Godot.SourceGenerators private static void GeneratePropertyGetter( string propertyMemberName, + ITypeSymbol propertyTypeSymbol, MarshalType propertyMarshalType, StringBuilder source, bool isFirstEntry @@ -317,7 +319,8 @@ namespace Godot.SourceGenerators .Append(propertyMemberName) .Append(") {\n") .Append(" value = ") - .AppendManagedToNativeVariantExpr(propertyMemberName, propertyMarshalType) + .AppendManagedToNativeVariantExpr("this." + propertyMemberName, + propertyTypeSymbol, propertyMarshalType) .Append(";\n") .Append(" return true;\n") .Append(" }\n"); @@ -340,15 +343,15 @@ namespace Godot.SourceGenerators private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo) { - source.Append(" properties.Add(new(type: (Godot.Variant.Type)") + source.Append(" properties.Add(new(type: (global::Godot.Variant.Type)") .Append((int)propertyInfo.Type) .Append(", name: PropertyName.") .Append(propertyInfo.Name) - .Append(", hint: (Godot.PropertyHint)") + .Append(", hint: (global::Godot.PropertyHint)") .Append((int)propertyInfo.Hint) .Append(", hintString: \"") .Append(propertyInfo.HintString) - .Append("\", usage: (Godot.PropertyUsageFlags)") + .Append("\", usage: (global::Godot.PropertyUsageFlags)") .Append((int)propertyInfo.Usage) .Append(", exported: ") .Append(propertyInfo.Exported ? "true" : "false") @@ -376,7 +379,8 @@ namespace Godot.SourceGenerators if (propertyUsage != PropertyUsageFlags.Category && attr.ConstructorArguments.Length > 1) hintString = attr.ConstructorArguments[1].Value?.ToString(); - yield return new PropertyInfo(VariantType.Nil, name, PropertyHint.None, hintString, propertyUsage.Value, true); + yield return new PropertyInfo(VariantType.Nil, name, PropertyHint.None, hintString, + propertyUsage.Value, true); } } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs index 98b9745c16..aa9dd9583e 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; @@ -66,21 +67,17 @@ namespace Godot.SourceGenerators { INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? - namespaceSymbol.FullQualifiedName() : + namespaceSymbol.FullQualifiedNameOmitGlobal() : string.Empty; bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; - string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + string uniqueHint = symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint() + "_ScriptPropertyDefVal.generated"; var source = new StringBuilder(); - source.Append("using Godot;\n"); - source.Append("using Godot.NativeInterop;\n"); - source.Append("\n"); - if (hasNamespace) { source.Append("namespace "); @@ -163,14 +160,71 @@ namespace Godot.SourceGenerators continue; } - // TODO: Detect default value from simple property getters (currently we only detect from initializers) - - EqualsValueClauseSyntax? initializer = property.DeclaringSyntaxReferences - .Select(r => r.GetSyntax() as PropertyDeclarationSyntax) - .Select(s => s?.Initializer ?? null) - .FirstOrDefault(); + var propertyDeclarationSyntax = property.DeclaringSyntaxReferences + .Select(r => r.GetSyntax() as PropertyDeclarationSyntax).FirstOrDefault(); - string? value = initializer?.Value.ToString(); + // Fully qualify the value to avoid issues with namespaces. + string? value = null; + if (propertyDeclarationSyntax != null) + { + if (propertyDeclarationSyntax.Initializer != null) + { + var sm = context.Compilation.GetSemanticModel(propertyDeclarationSyntax.Initializer.SyntaxTree); + value = propertyDeclarationSyntax.Initializer.Value.FullQualifiedSyntax(sm); + } + else + { + var propertyGet = propertyDeclarationSyntax.AccessorList?.Accessors + .Where(a => a.Keyword.IsKind(SyntaxKind.GetKeyword)).FirstOrDefault(); + if (propertyGet != null) + { + if (propertyGet.ExpressionBody != null) + { + if (propertyGet.ExpressionBody.Expression is IdentifierNameSyntax identifierNameSyntax) + { + var sm = context.Compilation.GetSemanticModel(identifierNameSyntax.SyntaxTree); + var fieldSymbol = sm.GetSymbolInfo(identifierNameSyntax).Symbol as IFieldSymbol; + EqualsValueClauseSyntax? initializer = fieldSymbol?.DeclaringSyntaxReferences + .Select(r => r.GetSyntax()) + .OfType<VariableDeclaratorSyntax>() + .Select(s => s.Initializer) + .FirstOrDefault(i => i != null); + + if (initializer != null) + { + sm = context.Compilation.GetSemanticModel(initializer.SyntaxTree); + value = initializer.Value.FullQualifiedSyntax(sm); + } + } + } + else + { + var returns = propertyGet.DescendantNodes().OfType<ReturnStatementSyntax>(); + if (returns.Count() == 1) + { + // Generate only single return + var returnStatementSyntax = returns.Single(); + if (returnStatementSyntax.Expression is IdentifierNameSyntax identifierNameSyntax) + { + var sm = context.Compilation.GetSemanticModel(identifierNameSyntax.SyntaxTree); + var fieldSymbol = sm.GetSymbolInfo(identifierNameSyntax).Symbol as IFieldSymbol; + EqualsValueClauseSyntax? initializer = fieldSymbol?.DeclaringSyntaxReferences + .Select(r => r.GetSyntax()) + .OfType<VariableDeclaratorSyntax>() + .Select(s => s.Initializer) + .FirstOrDefault(i => i != null); + + if (initializer != null) + { + sm = context.Compilation.GetSemanticModel(initializer.SyntaxTree); + value = initializer.Value.FullQualifiedSyntax(sm); + } + } + } + } + } + } + } exportedMembers.Add(new ExportedPropertyMetadata( property.Name, marshalType.Value, propertyType, value)); @@ -207,7 +261,13 @@ namespace Godot.SourceGenerators .Select(s => s.Initializer) .FirstOrDefault(i => i != null); - string? value = initializer?.Value.ToString(); + // This needs to be fully qualified to avoid issues with namespaces. + string? value = null; + if (initializer != null) + { + var sm = context.Compilation.GetSemanticModel(initializer.SyntaxTree); + value = initializer.Value.FullQualifiedSyntax(sm); + } exportedMembers.Add(new ExportedPropertyMetadata( field.Name, marshalType.Value, fieldType, value)); @@ -219,7 +279,8 @@ namespace Godot.SourceGenerators { source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); - string dictionaryType = "System.Collections.Generic.Dictionary<StringName, object>"; + string dictionaryType = + "global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant>"; source.Append("#if TOOLS\n"); source.Append(" internal new static "); @@ -237,7 +298,7 @@ namespace Godot.SourceGenerators string defaultValueLocalName = string.Concat("__", exportedMember.Name, "_default_value"); source.Append(" "); - source.Append(exportedMember.TypeSymbol.FullQualifiedName()); + source.Append(exportedMember.TypeSymbol.FullQualifiedNameIncludeGlobal()); source.Append(" "); source.Append(defaultValueLocalName); source.Append(" = "); @@ -246,7 +307,8 @@ namespace Godot.SourceGenerators source.Append(" values.Add(PropertyName."); source.Append(exportedMember.Name); source.Append(", "); - source.Append(defaultValueLocalName); + source.AppendManagedToVariantExpr(defaultValueLocalName, + exportedMember.TypeSymbol, exportedMember.Type); source.Append(");\n"); } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs index 11e0a6fa21..821f3af75f 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs @@ -66,13 +66,13 @@ namespace Godot.SourceGenerators { INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? - namespaceSymbol.FullQualifiedName() : + namespaceSymbol.FullQualifiedNameOmitGlobal() : string.Empty; bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; - string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + string uniqueHint = symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint() + "_ScriptSerialization.generated"; var source = new StringBuilder(); @@ -162,7 +162,8 @@ namespace Godot.SourceGenerators source.Append(" info.AddProperty(PropertyName.") .Append(propertyName) .Append(", ") - .AppendManagedToVariantExpr(string.Concat("this.", propertyName), property.Type) + .AppendManagedToVariantExpr(string.Concat("this.", propertyName), + property.PropertySymbol.Type, property.Type) .Append(");\n"); } @@ -175,7 +176,8 @@ namespace Godot.SourceGenerators source.Append(" info.AddProperty(PropertyName.") .Append(fieldName) .Append(", ") - .AppendManagedToVariantExpr(string.Concat("this.", fieldName), field.Type) + .AppendManagedToVariantExpr(string.Concat("this.", fieldName), + field.FieldSymbol.Type, field.Type) .Append(");\n"); } @@ -241,7 +243,7 @@ namespace Godot.SourceGenerators foreach (var signalDelegate in godotSignalDelegates) { string signalName = signalDelegate.Name; - string signalDelegateQualifiedName = signalDelegate.DelegateSymbol.FullQualifiedName(); + string signalDelegateQualifiedName = signalDelegate.DelegateSymbol.FullQualifiedNameIncludeGlobal(); source.Append(" if (info.TryGetSignalEventDelegate<") .Append(signalDelegateQualifiedName) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs index 50196b84f0..ba6c10aa31 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs @@ -75,13 +75,13 @@ namespace Godot.SourceGenerators { INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? - namespaceSymbol.FullQualifiedName() : + namespaceSymbol.FullQualifiedNameOmitGlobal() : string.Empty; bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; - string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + string uniqueHint = symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint() + "_ScriptSignals.generated"; var source = new StringBuilder(); @@ -176,14 +176,15 @@ namespace Godot.SourceGenerators source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); - source.Append($" public new class SignalName : {symbol.BaseType.FullQualifiedName()}.SignalName {{\n"); + source.Append( + $" public new class SignalName : {symbol.BaseType.FullQualifiedNameIncludeGlobal()}.SignalName {{\n"); // Generate cached StringNames for methods and properties, for fast lookup foreach (var signalDelegate in godotSignalDelegates) { string signalName = signalDelegate.Name; - source.Append(" public new static readonly StringName "); + source.Append(" public new static readonly global::Godot.StringName "); source.Append(signalName); source.Append(" = \""); source.Append(signalName); @@ -196,7 +197,7 @@ namespace Godot.SourceGenerators if (godotSignalDelegates.Count > 0) { - const string listType = "System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>"; + const string listType = "global::System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>"; source.Append(" internal new static ") .Append(listType) @@ -231,15 +232,16 @@ namespace Godot.SourceGenerators // as it doesn't emit the signal, only the event delegates. This can confuse users. // Maybe we should directly connect the delegates, as we do with native signals? source.Append(" private ") - .Append(signalDelegate.DelegateSymbol.FullQualifiedName()) + .Append(signalDelegate.DelegateSymbol.FullQualifiedNameIncludeGlobal()) .Append(" backing_") .Append(signalName) .Append(";\n"); - source.Append($" /// <inheritdoc cref=\"{signalDelegate.DelegateSymbol.FullQualifiedName()}\"/>\n"); + source.Append( + $" /// <inheritdoc cref=\"{signalDelegate.DelegateSymbol.FullQualifiedNameIncludeGlobal()}\"/>\n"); source.Append(" public event ") - .Append(signalDelegate.DelegateSymbol.FullQualifiedName()) + .Append(signalDelegate.DelegateSymbol.FullQualifiedNameIncludeGlobal()) .Append(" ") .Append(signalName) .Append(" {\n") @@ -300,7 +302,7 @@ namespace Godot.SourceGenerators AppendPropertyInfo(source, methodInfo.ReturnVal); - source.Append(", flags: (Godot.MethodFlags)") + source.Append(", flags: (global::Godot.MethodFlags)") .Append((int)methodInfo.Flags) .Append(", arguments: "); @@ -328,15 +330,15 @@ namespace Godot.SourceGenerators private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo) { - source.Append("new(type: (Godot.Variant.Type)") + source.Append("new(type: (global::Godot.Variant.Type)") .Append((int)propertyInfo.Type) .Append(", name: \"") .Append(propertyInfo.Name) - .Append("\", hint: (Godot.PropertyHint)") + .Append("\", hint: (global::Godot.PropertyHint)") .Append((int)propertyInfo.Hint) .Append(", hintString: \"") .Append(propertyInfo.HintString) - .Append("\", usage: (Godot.PropertyUsageFlags)") + .Append("\", usage: (global::Godot.PropertyUsageFlags)") .Append((int)propertyInfo.Usage) .Append(", exported: ") .Append(propertyInfo.Exported ? "true" : "false") @@ -351,7 +353,7 @@ namespace Godot.SourceGenerators if (invokeMethodData.RetType != null) { - returnVal = DeterminePropertyInfo(invokeMethodData.RetType.Value, name: string.Empty); + returnVal = DeterminePropertyInfo(invokeMethodData.RetType.Value.MarshalType, name: string.Empty); } else { diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/SemaphoreExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/SemaphoreExtensions.cs index 9d593fbf8a..ab21527344 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/SemaphoreExtensions.cs +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/SemaphoreExtensions.cs @@ -13,7 +13,7 @@ namespace GodotTools.IdeMessaging.Utils return waitAsyncTask.ContinueWith<IDisposable>(t => wrapper, cancellationToken).ConfigureAwait(false); } - private struct SemaphoreSlimWaitReleaseWrapper : IDisposable + private readonly struct SemaphoreSlimWaitReleaseWrapper : IDisposable { private readonly SemaphoreSlim semaphoreSlim; diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs index 2184cae6d6..94efcba3f1 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs @@ -22,7 +22,7 @@ namespace GodotTools.Export public bool FullAot; private bool _useInterpreter; - public bool UseInterpreter { get => _useInterpreter && !LLVMOnly; set => _useInterpreter = value; } + public bool UseInterpreter { readonly get => _useInterpreter && !LLVMOnly; set => _useInterpreter = value; } public string[] ExtraAotOptions; public string[] ExtraOptimizerOptions; diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 89364d1c02..de10c04e31 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -57,24 +57,22 @@ namespace GodotTools { pr.Step("Generating C# project...".TTR()); - string resourceDir = ProjectSettings.GlobalizePath("res://"); - - string path = resourceDir; + string csprojDir = Path.GetDirectoryName(GodotSharpDirs.ProjectCsProjPath); + string slnDir = Path.GetDirectoryName(GodotSharpDirs.ProjectSlnPath); string name = GodotSharpDirs.ProjectAssemblyName; - - string guid = CsProjOperations.GenerateGameProject(path, name); + string guid = CsProjOperations.GenerateGameProject(csprojDir, name); if (guid.Length > 0) { var solution = new DotNetSolution(name) { - DirectoryPath = path + DirectoryPath = slnDir }; var projectInfo = new DotNetSolution.ProjectInfo { Guid = guid, - PathRelativeToSolution = name + ".csproj", + PathRelativeToSolution = Path.GetRelativePath(slnDir, GodotSharpDirs.ProjectCsProjPath), Configs = new List<string> { "Debug", "ExportDebug", "ExportRelease" } }; @@ -375,6 +373,8 @@ namespace GodotTools { base._EnablePlugin(); + ProjectSettingsChanged += GodotSharpDirs.DetermineProjectLocation; + if (Instance != null) throw new InvalidOperationException(); Instance = this; @@ -455,7 +455,7 @@ namespace GodotTools _menuPopup.IdPressed += _MenuOptionPressed; // External editor settings - EditorDef("mono/editor/external_editor", ExternalEditorId.None); + EditorDef("mono/editor/external_editor", Variant.From(ExternalEditorId.None)); string settingsHintStr = "Disabled"; diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs index acb7cc3ab0..45ae7eb86b 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs @@ -1,3 +1,4 @@ +using Godot; using Godot.NativeInterop; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -8,30 +9,31 @@ namespace GodotTools.Internals { public static float EditorScale => Internal.godot_icall_Globals_EditorScale(); - public static unsafe object GlobalDef(string setting, object defaultValue, bool restartIfChanged = false) + // ReSharper disable once UnusedMethodReturnValue.Global + public static Variant GlobalDef(string setting, Variant defaultValue, bool restartIfChanged = false) { using godot_string settingIn = Marshaling.ConvertStringToNative(setting); - using godot_variant defaultValueIn = Marshaling.ConvertManagedObjectToVariant(defaultValue); - Internal.godot_icall_Globals_GlobalDef(settingIn, defaultValueIn, restartIfChanged, out godot_variant result); - using (result) - return Marshaling.ConvertVariantToManagedObject(result); + using godot_variant defaultValueIn = defaultValue.CopyNativeVariant(); + Internal.godot_icall_Globals_GlobalDef(settingIn, defaultValueIn, restartIfChanged, + out godot_variant result); + return Variant.CreateTakingOwnershipOfDisposableValue(result); } - public static unsafe object EditorDef(string setting, object defaultValue, bool restartIfChanged = false) + // ReSharper disable once UnusedMethodReturnValue.Global + public static Variant EditorDef(string setting, Variant defaultValue, bool restartIfChanged = false) { using godot_string settingIn = Marshaling.ConvertStringToNative(setting); - using godot_variant defaultValueIn = Marshaling.ConvertManagedObjectToVariant(defaultValue); - Internal.godot_icall_Globals_EditorDef(settingIn, defaultValueIn, restartIfChanged, out godot_variant result); - using (result) - return Marshaling.ConvertVariantToManagedObject(result); + using godot_variant defaultValueIn = defaultValue.CopyNativeVariant(); + Internal.godot_icall_Globals_EditorDef(settingIn, defaultValueIn, restartIfChanged, + out godot_variant result); + return Variant.CreateTakingOwnershipOfDisposableValue(result); } - public static object EditorShortcut(string setting) + public static Variant EditorShortcut(string setting) { using godot_string settingIn = Marshaling.ConvertStringToNative(setting); Internal.godot_icall_Globals_EditorShortcut(settingIn, out godot_variant result); - using (result) - return Marshaling.ConvertVariantToManagedObject(result); + return Variant.CreateTakingOwnershipOfDisposableValue(result); } [SuppressMessage("ReSharper", "InconsistentNaming")] diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs index 14285cc0f1..4e892be55c 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs @@ -52,10 +52,9 @@ namespace GodotTools.Internals { GlobalDef("dotnet/project/assembly_name", ""); GlobalDef("dotnet/project/solution_directory", ""); - GlobalDef("dotnet/project/c#_project_directory", ""); } - private static void DetermineProjectLocation() + public static void DetermineProjectLocation() { static string DetermineProjectName() { @@ -76,10 +75,11 @@ namespace GodotTools.Internals string slnParentDir = (string)ProjectSettings.GetSetting("dotnet/project/solution_directory"); if (string.IsNullOrEmpty(slnParentDir)) slnParentDir = "res://"; + else if (!slnParentDir.StartsWith("res://")) + slnParentDir = "res://" + slnParentDir; - string csprojParentDir = (string)ProjectSettings.GetSetting("dotnet/project/c#_project_directory"); - if (string.IsNullOrEmpty(csprojParentDir)) - csprojParentDir = "res://"; + // The csproj should be in the same folder as project.godot. + string csprojParentDir = "res://"; _projectSlnPath = Path.Combine(ProjectSettings.GlobalizePath(slnParentDir), string.Concat(_projectAssemblyName, ".sln")); diff --git a/modules/mono/editor/GodotTools/GodotTools/PlaySettings.cs b/modules/mono/editor/GodotTools/GodotTools/PlaySettings.cs index 820d0c0b83..9a8fdcc7c5 100644 --- a/modules/mono/editor/GodotTools/GodotTools/PlaySettings.cs +++ b/modules/mono/editor/GodotTools/GodotTools/PlaySettings.cs @@ -1,6 +1,6 @@ namespace GodotTools { - public struct PlaySettings + public readonly struct PlaySettings { public bool HasDebugger { get; } public string DebuggerHost { get; } diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index b90321b586..9f0bc3fbe3 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -2274,7 +2274,7 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf p_output.append(");\n"); // Generate Callable trampoline for the delegate - p_output << MEMBER_BEGIN "private static unsafe void " << p_isignal.proxy_name << "Trampoline" + p_output << MEMBER_BEGIN "private static void " << p_isignal.proxy_name << "Trampoline" << "(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)\n" << INDENT1 "{\n" << INDENT2 "Callable.ThrowIfArgCountMismatch(args, " << itos(p_isignal.arguments.size()) << ");\n" @@ -2289,9 +2289,8 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf p_output << ","; } - // TODO: We don't need to use VariantConversionCallbacks. We have the type information so we can use [cs_variant_to_managed] and [cs_managed_to_variant]. - p_output << "\n" INDENT3 "VariantConversionCallbacks.GetToManagedCallback<" - << arg_type->cs_type << ">()(args[" << itos(idx) << "])"; + p_output << sformat(arg_type->cs_variant_to_managed, + "args[" + itos(idx) + "]", arg_type->cs_type, arg_type->name); idx++; } @@ -2543,15 +2542,13 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall, << INDENT2 "int total_length = " << real_argc_str << " + vararg_length;\n"; r_output << INDENT2 "Span<godot_variant.movable> varargs_span = vararg_length <= VarArgsSpanThreshold ?\n" - << INDENT3 "stackalloc godot_variant.movable[VarArgsSpanThreshold].Cleared() :\n" + << INDENT3 "stackalloc godot_variant.movable[VarArgsSpanThreshold] :\n" << INDENT3 "new godot_variant.movable[vararg_length];\n"; r_output << INDENT2 "Span<IntPtr> " C_LOCAL_PTRCALL_ARGS "_span = total_length <= VarArgsSpanThreshold ?\n" << INDENT3 "stackalloc IntPtr[VarArgsSpanThreshold] :\n" << INDENT3 "new IntPtr[total_length];\n"; - r_output << INDENT2 "using var variantSpanDisposer = new VariantSpanDisposer(varargs_span);\n"; - r_output << INDENT2 "fixed (godot_variant.movable* varargs = &MemoryMarshal.GetReference(varargs_span))\n" << INDENT2 "fixed (IntPtr* " C_LOCAL_PTRCALL_ARGS " = " "&MemoryMarshal.GetReference(" C_LOCAL_PTRCALL_ARGS "_span))\n" @@ -2840,9 +2837,6 @@ bool BindingsGenerator::_populate_object_type_interfaces() { itype.is_ref_counted = ClassDB::is_parent_class(type_cname, name_cache.type_RefCounted); itype.memory_own = itype.is_ref_counted; - itype.cs_variant_to_managed = "(%1)VariantUtils.ConvertToGodotObject(%0)"; - itype.cs_managed_to_variant = "VariantUtils.CreateFromGodotObject(%0)"; - itype.c_out = "%5return "; itype.c_out += C_METHOD_UNMANAGED_GET_MANAGED; itype.c_out += itype.is_ref_counted ? "(%1.Reference);\n" : "(%1);\n"; @@ -3221,8 +3215,6 @@ bool BindingsGenerator::_populate_object_type_interfaces() { enum_itype.cname = StringName(enum_itype.name); enum_itype.proxy_name = itype.proxy_name + "." + enum_proxy_name; TypeInterface::postsetup_enum_type(enum_itype); - enum_itype.cs_variant_to_managed = "(%1)VariantUtils.ConvertToInt32(%0)"; - enum_itype.cs_managed_to_variant = "VariantUtils.CreateFromInt((int)%0)"; enum_types.insert(enum_itype.cname, enum_itype); } @@ -3451,16 +3443,14 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { TypeInterface itype; -#define INSERT_STRUCT_TYPE(m_type) \ - { \ - itype = TypeInterface::create_value_type(String(#m_type)); \ - itype.c_type_in = #m_type "*"; \ - itype.c_type_out = itype.cs_type; \ - itype.cs_in_expr = "&%0"; \ - itype.cs_in_expr_is_unsafe = true; \ - itype.cs_variant_to_managed = "VariantUtils.ConvertTo%2(%0)"; \ - itype.cs_managed_to_variant = "VariantUtils.CreateFrom%2(%0)"; \ - builtin_types.insert(itype.cname, itype); \ +#define INSERT_STRUCT_TYPE(m_type) \ + { \ + itype = TypeInterface::create_value_type(String(#m_type)); \ + itype.c_type_in = #m_type "*"; \ + itype.c_type_out = itype.cs_type; \ + itype.cs_in_expr = "&%0"; \ + itype.cs_in_expr_is_unsafe = true; \ + builtin_types.insert(itype.cname, itype); \ } INSERT_STRUCT_TYPE(Vector2) @@ -3491,8 +3481,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type_out = itype.c_type; itype.c_arg_in = "&%s"; itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromBool(%1);\n"; - itype.cs_variant_to_managed = "VariantUtils.ConvertToBool(%0)"; - itype.cs_managed_to_variant = "VariantUtils.CreateFromBool(%0)"; builtin_types.insert(itype.cname, itype); // Integer types @@ -3513,8 +3501,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type_in = itype.name; \ itype.c_type_out = itype.name; \ itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromInt(%1);\n"; \ - itype.cs_variant_to_managed = "VariantUtils.ConvertTo" m_int_struct_name "(%0)"; \ - itype.cs_managed_to_variant = "VariantUtils.CreateFromInt(%0)"; \ builtin_types.insert(itype.cname, itype); \ } @@ -3550,8 +3536,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type_in = itype.proxy_name; itype.c_type_out = itype.proxy_name; itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromFloat(%1);\n"; - itype.cs_variant_to_managed = "VariantUtils.ConvertToFloat32(%0)"; - itype.cs_managed_to_variant = "VariantUtils.CreateFromFloat(%0)"; builtin_types.insert(itype.cname, itype); // double @@ -3565,8 +3549,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type_in = itype.proxy_name; itype.c_type_out = itype.proxy_name; itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromFloat(%1);\n"; - itype.cs_variant_to_managed = "VariantUtils.ConvertToFloat64(%0)"; - itype.cs_managed_to_variant = "VariantUtils.CreateFromFloat(%0)"; builtin_types.insert(itype.cname, itype); } @@ -3584,8 +3566,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type_out = itype.cs_type; itype.c_type_is_disposable_struct = true; itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromString(%1);\n"; - itype.cs_variant_to_managed = "VariantUtils.ConvertToStringObject(%0)"; - itype.cs_managed_to_variant = "VariantUtils.CreateFromString(%0)"; builtin_types.insert(itype.cname, itype); // StringName @@ -3604,8 +3584,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromStringName(%1);\n"; itype.c_type_is_disposable_struct = false; // [c_out] takes ownership itype.c_ret_needs_default_initialization = true; - itype.cs_variant_to_managed = "VariantUtils.ConvertToStringNameObject(%0)"; - itype.cs_managed_to_variant = "VariantUtils.CreateFromStringName(%0)"; builtin_types.insert(itype.cname, itype); // NodePath @@ -3623,8 +3601,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type_out = itype.cs_type; itype.c_type_is_disposable_struct = false; // [c_out] takes ownership itype.c_ret_needs_default_initialization = true; - itype.cs_variant_to_managed = "VariantUtils.ConvertToNodePathObject(%0)"; - itype.cs_managed_to_variant = "VariantUtils.CreateFromNodePath(%0)"; builtin_types.insert(itype.cname, itype); // RID @@ -3637,8 +3613,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type = itype.cs_type; itype.c_type_in = itype.c_type; itype.c_type_out = itype.c_type; - itype.cs_variant_to_managed = "VariantUtils.ConvertToRID(%0)"; - itype.cs_managed_to_variant = "VariantUtils.CreateFromRID(%0)"; builtin_types.insert(itype.cname, itype); // Variant @@ -3655,8 +3629,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type_out = itype.cs_type; itype.c_type_is_disposable_struct = false; // [c_out] takes ownership itype.c_ret_needs_default_initialization = true; - itype.cs_variant_to_managed = "Variant.CreateCopyingBorrowed(%0)"; - itype.cs_managed_to_variant = "%0.CopyNativeVariant()"; builtin_types.insert(itype.cname, itype); // Callable @@ -3669,8 +3641,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type_in = "in " + itype.cs_type; itype.c_type_out = itype.cs_type; itype.c_type_is_disposable_struct = true; - itype.cs_variant_to_managed = "VariantUtils.ConvertToCallableManaged(%0)"; - itype.cs_managed_to_variant = "VariantUtils.CreateFromCallable(%0)"; builtin_types.insert(itype.cname, itype); // Signal @@ -3687,8 +3657,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type_in = "in " + itype.cs_type; itype.c_type_out = itype.cs_type; itype.c_type_is_disposable_struct = true; - itype.cs_variant_to_managed = "VariantUtils.ConvertToSignalInfo(%0)"; - itype.cs_managed_to_variant = "VariantUtils.CreateFromSignalInfo(%0)"; builtin_types.insert(itype.cname, itype); // VarArg (fictitious type to represent variable arguments) @@ -3718,8 +3686,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type_in = itype.proxy_name; \ itype.c_type_out = itype.proxy_name; \ itype.c_type_is_disposable_struct = true; \ - itype.cs_variant_to_managed = "VariantUtils.ConvertAs%2ToSystemArray(%0)"; \ - itype.cs_managed_to_variant = "VariantUtils.CreateFrom%2(%0)"; \ builtin_types.insert(itype.name, itype); \ } @@ -3755,8 +3721,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type_out = itype.cs_type; itype.c_type_is_disposable_struct = false; // [c_out] takes ownership itype.c_ret_needs_default_initialization = true; - itype.cs_variant_to_managed = "VariantUtils.ConvertToArrayObject(%0)"; - itype.cs_managed_to_variant = "VariantUtils.CreateFromArray(%0)"; builtin_types.insert(itype.cname, itype); // Array_@generic @@ -3764,6 +3728,9 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.name = "Array_@generic"; itype.cname = itype.name; itype.cs_out = "%5return new %2(%0(%1));"; + // For generic Godot collections, Variant.From<T>/As<T> is slower, so we need this special case + itype.cs_variant_to_managed = "VariantUtils.ConvertToArrayObject(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromArray(%0)"; builtin_types.insert(itype.cname, itype); // Dictionary @@ -3781,8 +3748,6 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.c_type_out = itype.cs_type; itype.c_type_is_disposable_struct = false; // [c_out] takes ownership itype.c_ret_needs_default_initialization = true; - itype.cs_variant_to_managed = "VariantUtils.ConvertToDictionaryObject(%0)"; - itype.cs_managed_to_variant = "VariantUtils.CreateFromDictionary(%0)"; builtin_types.insert(itype.cname, itype); // Dictionary_@generic @@ -3790,6 +3755,9 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.name = "Dictionary_@generic"; itype.cname = itype.name; itype.cs_out = "%5return new %2(%0(%1));"; + // For generic Godot collections, Variant.From<T>/As<T> is slower, so we need this special case + itype.cs_variant_to_managed = "VariantUtils.ConvertToDictionaryObject(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromDictionary(%0)"; builtin_types.insert(itype.cname, itype); // void (fictitious type to represent the return type of methods that do not return anything) @@ -3855,8 +3823,6 @@ void BindingsGenerator::_populate_global_constants() { enum_itype.cname = ienum.cname; enum_itype.proxy_name = enum_itype.name; TypeInterface::postsetup_enum_type(enum_itype); - enum_itype.cs_variant_to_managed = "(%1)VariantUtils.ConvertToInt32(%0)"; - enum_itype.cs_managed_to_variant = "VariantUtils.CreateFromInt((int)%0)"; enum_types.insert(enum_itype.cname, enum_itype); int prefix_length = _determine_enum_prefix(ienum); @@ -3889,8 +3855,6 @@ void BindingsGenerator::_populate_global_constants() { enum_itype.cname = enum_cname; enum_itype.proxy_name = enum_itype.name; TypeInterface::postsetup_enum_type(enum_itype); - enum_itype.cs_variant_to_managed = "(%1)VariantUtils.ConvertToInt32(%0)"; - enum_itype.cs_managed_to_variant = "VariantUtils.CreateFromInt((int)%0)"; enum_types.insert(enum_itype.cname, enum_itype); } } diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index a479c44368..6d172f4fb8 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -209,7 +209,7 @@ class BindingsGenerator { String name; StringName cname; - int type_parameter_count; + int type_parameter_count = 0; /** * Identifier name of the base class. @@ -514,7 +514,12 @@ class BindingsGenerator { static void postsetup_enum_type(TypeInterface &r_enum_itype); - TypeInterface() {} + TypeInterface() { + static String default_cs_variant_to_managed = "VariantUtils.ConvertTo<%1>(%0)"; + static String default_cs_managed_to_variant = "VariantUtils.CreateFrom<%1>(%0)"; + cs_variant_to_managed = default_cs_variant_to_managed; + cs_managed_to_variant = default_cs_managed_to_variant; + } }; struct InternalCall { diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/CallbacksInfo.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/CallbacksInfo.cs index 686023a077..ae51c07386 100644 --- a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/CallbacksInfo.cs +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/CallbacksInfo.cs @@ -4,7 +4,7 @@ using Microsoft.CodeAnalysis; namespace Godot.SourceGenerators.Internal; -internal struct CallbacksData +internal readonly struct CallbacksData { public CallbacksData(INamedTypeSymbol nativeTypeSymbol, INamedTypeSymbol funcStructSymbol) { diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Common.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Common.cs index 16e96c725a..d3726d69f0 100644 --- a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Common.cs +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Common.cs @@ -12,7 +12,7 @@ internal static class Common { string message = "Missing partial modifier on declaration of type '" + - $"{symbol.FullQualifiedName()}' which has attribute '{GeneratorClasses.GenerateUnmanagedCallbacksAttr}'"; + $"{symbol.FullQualifiedNameOmitGlobal()}' which has attribute '{GeneratorClasses.GenerateUnmanagedCallbacksAttr}'"; string description = $"{message}. Classes with attribute '{GeneratorClasses.GenerateUnmanagedCallbacksAttr}' " + "must be declared with the partial modifier."; @@ -39,7 +39,7 @@ internal static class Common .GetDeclaredSymbol(outerTypeDeclSyntax); string fullQualifiedName = outerSymbol is INamedTypeSymbol namedTypeSymbol ? - namedTypeSymbol.FullQualifiedName() : + namedTypeSymbol.FullQualifiedNameOmitGlobal() : "type not found"; string message = diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs index fac362479a..37f7005d01 100644 --- a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs @@ -94,13 +94,6 @@ internal static class ExtensionMethods }; } - private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } = - SymbolDisplayFormat.FullyQualifiedFormat - .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); - - public static string FullQualifiedName(this ITypeSymbol symbol) - => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal); - public static string NameWithTypeParameters(this INamedTypeSymbol symbol) { return symbol.IsGenericType ? @@ -108,8 +101,25 @@ internal static class ExtensionMethods symbol.Name; } - public static string FullQualifiedName(this INamespaceSymbol symbol) - => symbol.ToDisplayString(FullyQualifiedFormatOmitGlobal); + private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } = + SymbolDisplayFormat.FullyQualifiedFormat + .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); + + private static SymbolDisplayFormat FullyQualifiedFormatIncludeGlobal { get; } = + SymbolDisplayFormat.FullyQualifiedFormat + .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Included); + + public static string FullQualifiedNameOmitGlobal(this ITypeSymbol symbol) + => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal); + + public static string FullQualifiedNameOmitGlobal(this INamespaceSymbol namespaceSymbol) + => namespaceSymbol.ToDisplayString(FullyQualifiedFormatOmitGlobal); + + public static string FullQualifiedNameIncludeGlobal(this ITypeSymbol symbol) + => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatIncludeGlobal); + + public static string FullQualifiedNameIncludeGlobal(this INamespaceSymbol namespaceSymbol) + => namespaceSymbol.ToDisplayString(FullyQualifiedFormatIncludeGlobal); public static string SanitizeQualifiedNameForUniqueHint(this string qualifiedName) => qualifiedName diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs index da578309bc..3226ca79e5 100644 --- a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs @@ -96,7 +96,7 @@ internal class GenerateUnmanagedCallbacksAttribute : Attribute INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? - namespaceSymbol.FullQualifiedName() : + namespaceSymbol.FullQualifiedNameOmitGlobal() : string.Empty; bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; @@ -144,7 +144,7 @@ using Godot.NativeInterop; source.Append("[System.Runtime.CompilerServices.SkipLocalsInit]\n"); source.Append($"unsafe partial class {symbol.Name}\n"); source.Append("{\n"); - source.Append($" private static {data.FuncStructSymbol.FullQualifiedName()} _unmanagedCallbacks;\n\n"); + source.Append($" private static {data.FuncStructSymbol.FullQualifiedNameIncludeGlobal()} _unmanagedCallbacks;\n\n"); foreach (var callback in data.Methods) { @@ -159,7 +159,7 @@ using Godot.NativeInterop; source.Append("static "); source.Append("partial "); - source.Append(callback.ReturnType.FullQualifiedName()); + source.Append(callback.ReturnType.FullQualifiedNameIncludeGlobal()); source.Append(' '); source.Append(callback.Name); source.Append('('); @@ -228,7 +228,7 @@ using Godot.NativeInterop; if (!callback.ReturnsVoid) { if (methodSourceAfterCall.Length != 0) - source.Append($"{callback.ReturnType.FullQualifiedName()} ret = "); + source.Append($"{callback.ReturnType.FullQualifiedNameIncludeGlobal()} ret = "); else source.Append("return "); } @@ -267,7 +267,7 @@ using Godot.NativeInterop; source.Append("\n\n#pragma warning restore CA1707\n"); - context.AddSource($"{data.NativeTypeSymbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()}.generated", + context.AddSource($"{data.NativeTypeSymbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint()}.generated", SourceText.From(source.ToString(), Encoding.UTF8)); } @@ -277,7 +277,7 @@ using Godot.NativeInterop; INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? - namespaceSymbol.FullQualifiedName() : + namespaceSymbol.FullQualifiedNameOmitGlobal() : string.Empty; bool hasNamespace = classNs.Length != 0; bool isInnerClass = symbol.ContainingType != null; @@ -338,18 +338,18 @@ using Godot.NativeInterop; // just pass it by-ref and let it be pinned. AppendRefKind(source, parameter.RefKind) .Append(' ') - .Append(parameter.Type.FullQualifiedName()); + .Append(parameter.Type.FullQualifiedNameIncludeGlobal()); } } else { - source.Append(parameter.Type.FullQualifiedName()); + source.Append(parameter.Type.FullQualifiedNameIncludeGlobal()); } source.Append(", "); } - source.Append(callback.ReturnType.FullQualifiedName()); + source.Append(callback.ReturnType.FullQualifiedNameIncludeGlobal()); source.Append($"> {callback.Name};\n"); } @@ -372,12 +372,12 @@ using Godot.NativeInterop; source.Append("\n#pragma warning restore CA1707\n"); - context.AddSource($"{symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()}.generated", + context.AddSource($"{symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint()}.generated", SourceText.From(source.ToString(), Encoding.UTF8)); } private static bool IsGodotInteropStruct(ITypeSymbol type) => - GodotInteropStructs.Contains(type.FullQualifiedName()); + GodotInteropStructs.Contains(type.FullQualifiedNameOmitGlobal()); private static bool IsByRefParameter(IParameterSymbol parameter) => parameter.RefKind is RefKind.In or RefKind.Out or RefKind.Ref; @@ -393,7 +393,7 @@ using Godot.NativeInterop; private static void AppendPointerType(StringBuilder source, ITypeSymbol type) { - source.Append(type.FullQualifiedName()); + source.Append(type.FullQualifiedNameIncludeGlobal()); source.Append('*'); } @@ -426,7 +426,7 @@ using Godot.NativeInterop; { varName = $"{parameter.Name}_copy"; - source.Append(parameter.Type.FullQualifiedName()); + source.Append(parameter.Type.FullQualifiedNameIncludeGlobal()); source.Append(' '); source.Append(varName); if (parameter.RefKind is RefKind.In or RefKind.Ref) diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs index 8308bada24..4ce02d221e 100644 --- a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs +++ b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs @@ -28,17 +28,24 @@ namespace GodotPlugins get => _pluginLoadContext?.AssemblyLoadedPath; } + public bool IsCollectible + { + [MethodImpl(MethodImplOptions.NoInlining)] + get => _pluginLoadContext?.IsCollectible ?? false; + } + [MethodImpl(MethodImplOptions.NoInlining)] public static (Assembly, PluginLoadContextWrapper) CreateAndLoadFromAssemblyName( AssemblyName assemblyName, string pluginPath, ICollection<string> sharedAssemblies, - AssemblyLoadContext mainLoadContext + AssemblyLoadContext mainLoadContext, + bool isCollectible ) { var wrapper = new PluginLoadContextWrapper(); wrapper._pluginLoadContext = new PluginLoadContext( - pluginPath, sharedAssemblies, mainLoadContext); + pluginPath, sharedAssemblies, mainLoadContext, isCollectible); var assembly = wrapper._pluginLoadContext.LoadFromAssemblyName(assemblyName); return (assembly, wrapper); } @@ -61,6 +68,7 @@ namespace GodotPlugins private static readonly Assembly CoreApiAssembly = typeof(Godot.Object).Assembly; private static Assembly? _editorApiAssembly; private static PluginLoadContextWrapper? _projectLoadContext; + private static bool _editorHint = false; private static readonly AssemblyLoadContext MainLoadContext = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ?? @@ -77,15 +85,17 @@ namespace GodotPlugins { try { + _editorHint = editorHint.ToBool(); + _dllImportResolver = new GodotDllImportResolver(godotDllHandle).OnResolveDllImport; SharedAssemblies.Add(CoreApiAssembly.GetName()); NativeLibrary.SetDllImportResolver(CoreApiAssembly, _dllImportResolver); - AlcReloadCfg.Configure(alcReloadEnabled: editorHint.ToBool()); + AlcReloadCfg.Configure(alcReloadEnabled: _editorHint); NativeFuncs.Initialize(unmanagedCallbacks, unmanagedCallbacksSize); - if (editorHint.ToBool()) + if (_editorHint) { _editorApiAssembly = Assembly.Load("GodotSharpEditor"); SharedAssemblies.Add(_editorApiAssembly.GetName()); @@ -128,7 +138,7 @@ namespace GodotPlugins string assemblyPath = new(nAssemblyPath); - (var projectAssembly, _projectLoadContext) = LoadPlugin(assemblyPath); + (var projectAssembly, _projectLoadContext) = LoadPlugin(assemblyPath, isCollectible: _editorHint); string loadedAssemblyPath = _projectLoadContext.AssemblyLoadedPath ?? assemblyPath; *outLoadedAssemblyPath = Marshaling.ConvertStringToNative(loadedAssemblyPath); @@ -155,7 +165,7 @@ namespace GodotPlugins if (_editorApiAssembly == null) throw new InvalidOperationException("The Godot editor API assembly is not loaded."); - var (assembly, _) = LoadPlugin(assemblyPath); + var (assembly, _) = LoadPlugin(assemblyPath, isCollectible: _editorHint); NativeLibrary.SetDllImportResolver(assembly, _dllImportResolver!); @@ -180,7 +190,7 @@ namespace GodotPlugins } } - private static (Assembly, PluginLoadContextWrapper) LoadPlugin(string assemblyPath) + private static (Assembly, PluginLoadContextWrapper) LoadPlugin(string assemblyPath, bool isCollectible) { string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath); @@ -194,7 +204,7 @@ namespace GodotPlugins } return PluginLoadContextWrapper.CreateAndLoadFromAssemblyName( - new AssemblyName(assemblyName), assemblyPath, sharedAssemblies, MainLoadContext); + new AssemblyName(assemblyName), assemblyPath, sharedAssemblies, MainLoadContext, isCollectible); } [UnmanagedCallersOnly] @@ -218,6 +228,12 @@ namespace GodotPlugins if (pluginLoadContext == null) return true; + if (!pluginLoadContext.IsCollectible) + { + Console.Error.WriteLine("Cannot unload a non-collectible assembly load context."); + return false; + } + Console.WriteLine("Unloading assembly load context..."); var alcWeakReference = pluginLoadContext.CreateWeakReference(); diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs index dcd572c65e..344b76a202 100644 --- a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs +++ b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs @@ -15,8 +15,8 @@ namespace GodotPlugins public string? AssemblyLoadedPath { get; private set; } public PluginLoadContext(string pluginPath, ICollection<string> sharedAssemblies, - AssemblyLoadContext mainLoadContext) - : base(isCollectible: true) + AssemblyLoadContext mainLoadContext, bool isCollectible) + : base(isCollectible) { _resolver = new AssemblyDependencyResolver(pluginPath); _sharedAssemblies = sharedAssemblies; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs index d8a6644a66..a2916d4ae8 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs @@ -20,7 +20,7 @@ namespace Godot /// <value>Directly uses a private field.</value> public Vector3 Position { - get { return _position; } + readonly get { return _position; } set { _position = value; } } @@ -31,7 +31,7 @@ namespace Godot /// <value>Directly uses a private field.</value> public Vector3 Size { - get { return _size; } + readonly get { return _size; } set { _size = value; } } @@ -45,7 +45,7 @@ namespace Godot /// </value> public Vector3 End { - get { return _position + _size; } + readonly get { return _position + _size; } set { _size = value - _position; } } @@ -54,7 +54,7 @@ namespace Godot /// the most-negative corner is the origin and the size is positive. /// </summary> /// <returns>The modified <see cref="AABB"/>.</returns> - public AABB Abs() + public readonly AABB Abs() { Vector3 end = End; Vector3 topLeft = new Vector3(Mathf.Min(_position.x, end.x), Mathf.Min(_position.y, end.y), Mathf.Min(_position.z, end.z)); @@ -66,7 +66,7 @@ namespace Godot /// to <see cref="Position"/> + (<see cref="Size"/> / 2). /// </summary> /// <returns>The center.</returns> - public Vector3 GetCenter() + public readonly Vector3 GetCenter() { return _position + (_size * 0.5f); } @@ -78,7 +78,7 @@ namespace Godot /// <returns> /// A <see langword="bool"/> for whether or not this <see cref="AABB"/> encloses <paramref name="with"/>. /// </returns> - public bool Encloses(AABB with) + public readonly bool Encloses(AABB with) { Vector3 srcMin = _position; Vector3 srcMax = _position + _size; @@ -98,7 +98,7 @@ namespace Godot /// </summary> /// <param name="point">The point to include.</param> /// <returns>The expanded <see cref="AABB"/>.</returns> - public AABB Expand(Vector3 point) + public readonly AABB Expand(Vector3 point) { Vector3 begin = _position; Vector3 end = _position + _size; @@ -140,7 +140,7 @@ namespace Godot /// <paramref name="idx"/> is less than 0 or greater than 7. /// </exception> /// <returns>An endpoint of the <see cref="AABB"/>.</returns> - public Vector3 GetEndpoint(int idx) + public readonly Vector3 GetEndpoint(int idx) { switch (idx) { @@ -172,7 +172,7 @@ namespace Godot /// Returns the normalized longest axis of the <see cref="AABB"/>. /// </summary> /// <returns>A vector representing the normalized longest axis of the <see cref="AABB"/>.</returns> - public Vector3 GetLongestAxis() + public readonly Vector3 GetLongestAxis() { var axis = new Vector3(1f, 0f, 0f); real_t maxSize = _size.x; @@ -195,7 +195,7 @@ namespace Godot /// Returns the <see cref="Vector3.Axis"/> index of the longest axis of the <see cref="AABB"/>. /// </summary> /// <returns>A <see cref="Vector3.Axis"/> index for which axis is longest.</returns> - public Vector3.Axis GetLongestAxisIndex() + public readonly Vector3.Axis GetLongestAxisIndex() { var axis = Vector3.Axis.X; real_t maxSize = _size.x; @@ -218,7 +218,7 @@ namespace Godot /// Returns the scalar length of the longest axis of the <see cref="AABB"/>. /// </summary> /// <returns>The scalar length of the longest axis of the <see cref="AABB"/>.</returns> - public real_t GetLongestAxisSize() + public readonly real_t GetLongestAxisSize() { real_t maxSize = _size.x; @@ -235,7 +235,7 @@ namespace Godot /// Returns the normalized shortest axis of the <see cref="AABB"/>. /// </summary> /// <returns>A vector representing the normalized shortest axis of the <see cref="AABB"/>.</returns> - public Vector3 GetShortestAxis() + public readonly Vector3 GetShortestAxis() { var axis = new Vector3(1f, 0f, 0f); real_t maxSize = _size.x; @@ -258,7 +258,7 @@ namespace Godot /// Returns the <see cref="Vector3.Axis"/> index of the shortest axis of the <see cref="AABB"/>. /// </summary> /// <returns>A <see cref="Vector3.Axis"/> index for which axis is shortest.</returns> - public Vector3.Axis GetShortestAxisIndex() + public readonly Vector3.Axis GetShortestAxisIndex() { var axis = Vector3.Axis.X; real_t maxSize = _size.x; @@ -281,7 +281,7 @@ namespace Godot /// Returns the scalar length of the shortest axis of the <see cref="AABB"/>. /// </summary> /// <returns>The scalar length of the shortest axis of the <see cref="AABB"/>.</returns> - public real_t GetShortestAxisSize() + public readonly real_t GetShortestAxisSize() { real_t maxSize = _size.x; @@ -300,7 +300,7 @@ namespace Godot /// </summary> /// <param name="dir">The direction to find support for.</param> /// <returns>A vector representing the support.</returns> - public Vector3 GetSupport(Vector3 dir) + public readonly Vector3 GetSupport(Vector3 dir) { Vector3 halfExtents = _size * 0.5f; Vector3 ofs = _position + halfExtents; @@ -315,7 +315,7 @@ namespace Godot /// Returns the volume of the <see cref="AABB"/>. /// </summary> /// <returns>The volume.</returns> - public real_t GetVolume() + public readonly real_t GetVolume() { return _size.x * _size.y * _size.z; } @@ -325,7 +325,7 @@ namespace Godot /// </summary> /// <param name="by">The amount to grow by.</param> /// <returns>The grown <see cref="AABB"/>.</returns> - public AABB Grow(real_t by) + public readonly AABB Grow(real_t by) { AABB res = this; @@ -347,7 +347,7 @@ namespace Godot /// <returns> /// A <see langword="bool"/> for whether or not the <see cref="AABB"/> contains <paramref name="point"/>. /// </returns> - public bool HasPoint(Vector3 point) + public readonly bool HasPoint(Vector3 point) { if (point.x < _position.x) return false; @@ -374,7 +374,7 @@ namespace Godot /// <returns> /// A <see langword="bool"/> for whether or not the <see cref="AABB"/> has surface. /// </returns> - public bool HasSurface() + public readonly bool HasSurface() { return _size.x > 0.0f || _size.y > 0.0f || _size.z > 0.0f; } @@ -388,7 +388,7 @@ namespace Godot /// <returns> /// A <see langword="bool"/> for whether or not the <see cref="AABB"/> has volume. /// </returns> - public bool HasVolume() + public readonly bool HasVolume() { return _size.x > 0.0f && _size.y > 0.0f && _size.z > 0.0f; } @@ -398,7 +398,7 @@ namespace Godot /// </summary> /// <param name="with">The other <see cref="AABB"/>.</param> /// <returns>The clipped <see cref="AABB"/>.</returns> - public AABB Intersection(AABB with) + public readonly AABB Intersection(AABB with) { Vector3 srcMin = _position; Vector3 srcMax = _position + _size; @@ -447,7 +447,7 @@ namespace Godot /// <returns> /// A <see langword="bool"/> for whether or not they are intersecting. /// </returns> - public bool Intersects(AABB with, bool includeBorders = false) + public readonly bool Intersects(AABB with, bool includeBorders = false) { if (includeBorders) { @@ -490,7 +490,7 @@ namespace Godot /// <returns> /// A <see langword="bool"/> for whether or not the <see cref="AABB"/> intersects the <see cref="Plane"/>. /// </returns> - public bool IntersectsPlane(Plane plane) + public readonly bool IntersectsPlane(Plane plane) { Vector3[] points = { @@ -531,7 +531,7 @@ namespace Godot /// <returns> /// A <see langword="bool"/> for whether or not the <see cref="AABB"/> intersects the line segment. /// </returns> - public bool IntersectsSegment(Vector3 from, Vector3 to) + public readonly bool IntersectsSegment(Vector3 from, Vector3 to) { real_t min = 0f; real_t max = 1f; @@ -590,7 +590,7 @@ namespace Godot /// </summary> /// <param name="with">The other <see cref="AABB"/>.</param> /// <returns>The merged <see cref="AABB"/>.</returns> - public AABB Merge(AABB with) + public readonly AABB Merge(AABB with) { Vector3 beg1 = _position; Vector3 beg2 = with._position; @@ -702,7 +702,7 @@ namespace Godot /// </summary> /// <param name="obj">The object to compare with.</param> /// <returns>Whether or not the AABB and the object are equal.</returns> - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { return obj is AABB other && Equals(other); } @@ -714,7 +714,7 @@ namespace Godot /// </summary> /// <param name="other">The other AABB.</param> /// <returns>Whether or not the AABBs are exactly equal.</returns> - public bool Equals(AABB other) + public readonly bool Equals(AABB other) { return _position == other._position && _size == other._size; } @@ -725,7 +725,7 @@ namespace Godot /// </summary> /// <param name="other">The other AABB to compare.</param> /// <returns>Whether or not the AABBs structures are approximately equal.</returns> - public bool IsEqualApprox(AABB other) + public readonly bool IsEqualApprox(AABB other) { return _position.IsEqualApprox(other._position) && _size.IsEqualApprox(other._size); } @@ -734,7 +734,7 @@ namespace Godot /// Serves as the hash function for <see cref="AABB"/>. /// </summary> /// <returns>A hash code for this AABB.</returns> - public override int GetHashCode() + public override readonly int GetHashCode() { return _position.GetHashCode() ^ _size.GetHashCode(); } @@ -743,7 +743,7 @@ namespace Godot /// Converts this <see cref="AABB"/> to a string. /// </summary> /// <returns>A string representation of this AABB.</returns> - public override string ToString() + public override readonly string ToString() { return $"{_position}, {_size}"; } @@ -752,7 +752,7 @@ namespace Godot /// Converts this <see cref="AABB"/> to a string with the given <paramref name="format"/>. /// </summary> /// <returns>A string representation of this AABB.</returns> - public string ToString(string format) + public readonly string ToString(string format) { return $"{_position.ToString(format)}, {_size.ToString(format)}"; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs index f1b46e293b..130776499b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs @@ -418,8 +418,8 @@ namespace Godot.Collections { for (int i = 0; i < count; i++) { - object obj = Marshaling.ConvertVariantToManagedObject(NativeValue.DangerousSelfRef.Elements[i]); - array.SetValue(obj, index); + object boxedVariant = Variant.CreateCopyingBorrowed(NativeValue.DangerousSelfRef.Elements[i]); + array.SetValue(boxedVariant, index); index++; } } @@ -474,6 +474,11 @@ namespace Godot.Collections } } + internal interface IGenericGodotArray + { + public Array UnderlyingArray { get; } + } + /// <summary> /// Typed wrapper around Godot's Array class, an array of Variant /// typed elements allocated in the engine in C++. Useful when @@ -487,7 +492,8 @@ namespace Godot.Collections IList<T>, IReadOnlyList<T>, ICollection<T>, - IEnumerable<T> + IEnumerable<T>, + IGenericGodotArray { private static godot_variant ToVariantFunc(in Array<T> godotArray) => VariantUtils.CreateFromArray(godotArray); @@ -495,39 +501,16 @@ namespace Godot.Collections private static Array<T> FromVariantFunc(in godot_variant variant) => VariantUtils.ConvertToArrayObject<T>(variant); - // ReSharper disable StaticMemberInGenericType - // Warning is about unique static fields being created for each generic type combination: - // https://www.jetbrains.com/help/resharper/StaticMemberInGenericType.html - // In our case this is exactly what we want. - - private static readonly unsafe delegate* managed<in T, godot_variant> ConvertToVariantCallback; - private static readonly unsafe delegate* managed<in godot_variant, T> ConvertToManagedCallback; - - // ReSharper restore StaticMemberInGenericType - static unsafe Array() { - VariantConversionCallbacks.GenericConversionCallbacks[typeof(Array<T>)] = - ( - (IntPtr)(delegate* managed<in Array<T>, godot_variant>)&ToVariantFunc, - (IntPtr)(delegate* managed<in godot_variant, Array<T>>)&FromVariantFunc - ); - - ConvertToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<T>(); - ConvertToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<T>(); - } - - private static unsafe void ValidateVariantConversionCallbacks() - { - if (ConvertToVariantCallback == null || ConvertToManagedCallback == null) - { - throw new InvalidOperationException( - $"The array element type is not supported for conversion to Variant: '{typeof(T).FullName}'."); - } + VariantUtils.GenericConversion<Array<T>>.ToVariantCb = &ToVariantFunc; + VariantUtils.GenericConversion<Array<T>>.FromVariantCb = &FromVariantFunc; } private readonly Array _underlyingArray; + Array IGenericGodotArray.UnderlyingArray => _underlyingArray; + internal ref godot_array.movable NativeValue { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -539,8 +522,6 @@ namespace Godot.Collections /// </summary> public Array() { - ValidateVariantConversionCallbacks(); - _underlyingArray = new Array(); } @@ -551,8 +532,6 @@ namespace Godot.Collections /// <returns>A new Godot Array.</returns> public Array(IEnumerable<T> collection) { - ValidateVariantConversionCallbacks(); - if (collection == null) throw new ArgumentNullException(nameof(collection)); @@ -569,8 +548,6 @@ namespace Godot.Collections /// <returns>A new Godot Array.</returns> public Array(T[] array) : this() { - ValidateVariantConversionCallbacks(); - if (array == null) throw new ArgumentNullException(nameof(array)); @@ -586,8 +563,6 @@ namespace Godot.Collections /// <param name="array">The untyped array to construct from.</param> public Array(Array array) { - ValidateVariantConversionCallbacks(); - _underlyingArray = array; } @@ -665,7 +640,7 @@ namespace Godot.Collections get { _underlyingArray.GetVariantBorrowElementAt(index, out godot_variant borrowElem); - return ConvertToManagedCallback(borrowElem); + return VariantUtils.ConvertTo<T>(borrowElem); } set { @@ -675,7 +650,7 @@ namespace Godot.Collections godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref self); godot_variant* itemPtr = &ptrw[index]; (*itemPtr).Dispose(); - *itemPtr = ConvertToVariantCallback(value); + *itemPtr = VariantUtils.CreateFrom(value); } } @@ -685,9 +660,9 @@ namespace Godot.Collections /// </summary> /// <param name="item">The item to search for.</param> /// <returns>The index of the item, or -1 if not found.</returns> - public unsafe int IndexOf(T item) + public int IndexOf(T item) { - using var variantValue = ConvertToVariantCallback(item); + using var variantValue = VariantUtils.CreateFrom(item); var self = (godot_array)_underlyingArray.NativeValue; return NativeFuncs.godotsharp_array_index_of(ref self, variantValue); } @@ -700,12 +675,12 @@ namespace Godot.Collections /// </summary> /// <param name="index">The index to insert at.</param> /// <param name="item">The item to insert.</param> - public unsafe void Insert(int index, T item) + public void Insert(int index, T item) { if (index < 0 || index > Count) throw new ArgumentOutOfRangeException(nameof(index)); - using var variantValue = ConvertToVariantCallback(item); + using var variantValue = VariantUtils.CreateFrom(item); var self = (godot_array)_underlyingArray.NativeValue; NativeFuncs.godotsharp_array_insert(ref self, index, variantValue); } @@ -736,9 +711,9 @@ namespace Godot.Collections /// </summary> /// <param name="item">The item to add.</param> /// <returns>The new size after adding the item.</returns> - public unsafe void Add(T item) + public void Add(T item) { - using var variantValue = ConvertToVariantCallback(item); + using var variantValue = VariantUtils.CreateFrom(item); var self = (godot_array)_underlyingArray.NativeValue; _ = NativeFuncs.godotsharp_array_add(ref self, variantValue); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs index b7d633517a..acdae83d2e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs @@ -1,20 +1,32 @@ using System; +using System.Diagnostics.CodeAnalysis; #nullable enable namespace Godot { /// <summary> - /// An attribute that determines if an assembly has scripts. If so, what types of scripts the assembly has. + /// Attribute that determines that the assembly contains Godot scripts and, optionally, the + /// collection of types that implement scripts; otherwise, retrieving the types requires lookup. /// </summary> [AttributeUsage(AttributeTargets.Assembly)] public class AssemblyHasScriptsAttribute : Attribute { + /// <summary> + /// If the Godot scripts contained in the assembly require lookup + /// and can't rely on <see cref="ScriptTypes"/>. + /// </summary> + [MemberNotNullWhen(false, nameof(ScriptTypes))] public bool RequiresLookup { get; } + + /// <summary> + /// The collection of types that implement a Godot script. + /// </summary> public Type[]? ScriptTypes { get; } /// <summary> - /// Constructs a new AssemblyHasScriptsAttribute instance. + /// Constructs a new AssemblyHasScriptsAttribute instance + /// that requires lookup to get the Godot scripts. /// </summary> public AssemblyHasScriptsAttribute() { @@ -23,9 +35,10 @@ namespace Godot } /// <summary> - /// Constructs a new AssemblyHasScriptsAttribute instance. + /// Constructs a new AssemblyHasScriptsAttribute instance + /// that includes the Godot script types and requires no lookup. /// </summary> - /// <param name="scriptTypes">The specified type(s) of scripts.</param> + /// <param name="scriptTypes">The collection of types that implement a Godot script.</param> public AssemblyHasScriptsAttribute(Type[] scriptTypes) { RequiresLookup = false; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportAttribute.cs index 3d204bdf9f..a48d79091f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportAttribute.cs @@ -3,23 +3,30 @@ using System; namespace Godot { /// <summary> - /// An attribute used to export objects. + /// Exports the annotated member as a property of the Godot Object. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class ExportAttribute : Attribute { - private PropertyHint hint; - private string hintString; + /// <summary> + /// Optional hint that determines how the property should be handled by the editor. + /// </summary> + public PropertyHint Hint { get; } + + /// <summary> + /// Optional string that can contain additional metadata for the <see cref="Hint"/>. + /// </summary> + public string HintString { get; } /// <summary> /// Constructs a new ExportAttribute Instance. /// </summary> - /// <param name="hint">A hint to the exported object.</param> - /// <param name="hintString">A string representing the exported object.</param> + /// <param name="hint">The hint for the exported property.</param> + /// <param name="hintString">A string that may contain additional metadata for the hint.</param> public ExportAttribute(PropertyHint hint = PropertyHint.None, string hintString = "") { - this.hint = hint; - this.hintString = hintString; + Hint = hint; + HintString = hintString; } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportCategoryAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportCategoryAttribute.cs index 101e56f8d3..2ae55acd3e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportCategoryAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportCategoryAttribute.cs @@ -8,7 +8,10 @@ namespace Godot [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class ExportCategoryAttribute : Attribute { - private string name; + /// <summary> + /// Name of the category. + /// </summary> + public string Name { get; } /// <summary> /// Define a new category for the following exported properties. @@ -16,7 +19,7 @@ namespace Godot /// <param name="name">The name of the category.</param> public ExportCategoryAttribute(string name) { - this.name = name; + Name = name; } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportGroupAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportGroupAttribute.cs index 3bd532cec1..82bd446640 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportGroupAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportGroupAttribute.cs @@ -1,5 +1,7 @@ using System; +#nullable enable + namespace Godot { /// <summary> @@ -8,8 +10,15 @@ namespace Godot [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class ExportGroupAttribute : Attribute { - private string name; - private string prefix; + /// <summary> + /// Name of the group. + /// </summary> + public string Name { get; } + + /// <summary> + /// If provided, the prefix that all properties must have to be considered part of the group. + /// </summary> + public string? Prefix { get; } /// <summary> /// Define a new group for the following exported properties. @@ -18,8 +27,8 @@ namespace Godot /// <param name="prefix">If provided, the group would make group to only consider properties that have this prefix.</param> public ExportGroupAttribute(string name, string prefix = "") { - this.name = name; - this.prefix = prefix; + Name = name; + Prefix = prefix; } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportSubgroupAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportSubgroupAttribute.cs index 2ae6eb0b68..3282b466f6 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportSubgroupAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportSubgroupAttribute.cs @@ -1,5 +1,7 @@ using System; +#nullable enable + namespace Godot { /// <summary> @@ -8,8 +10,15 @@ namespace Godot [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class ExportSubgroupAttribute : Attribute { - private string name; - private string prefix; + /// <summary> + /// Name of the subgroup. + /// </summary> + public string Name { get; } + + /// <summary> + /// If provided, the prefix that all properties must have to be considered part of the subgroup. + /// </summary> + public string? Prefix { get; } /// <summary> /// Define a new subgroup for the following exported properties. This helps to organize properties in the Inspector dock. @@ -18,8 +27,8 @@ namespace Godot /// <param name="prefix">If provided, the subgroup would make group to only consider properties that have this prefix.</param> public ExportSubgroupAttribute(string name, string prefix = "") { - this.name = name; - this.prefix = prefix; + Name = name; + Prefix = prefix; } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttribute.cs index fb37838ffa..afee926464 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttribute.cs @@ -19,17 +19,17 @@ namespace Godot /// <summary> /// If the method will also be called locally; otherwise, it is only called remotely. /// </summary> - public bool CallLocal { get; set; } = false; + public bool CallLocal { get; init; } = false; /// <summary> /// Transfer mode for the annotated method. /// </summary> - public MultiplayerPeer.TransferModeEnum TransferMode { get; set; } = MultiplayerPeer.TransferModeEnum.Reliable; + public MultiplayerPeer.TransferModeEnum TransferMode { get; init; } = MultiplayerPeer.TransferModeEnum.Reliable; /// <summary> /// Transfer channel for the annotated mode. /// </summary> - public int TransferChannel { get; set; } = 0; + public int TransferChannel { get; init; } = 0; /// <summary> /// Constructs a <see cref="RPCAttribute"/> instance. diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs index 2c8a53ae1c..f05bcdac38 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs @@ -8,6 +8,9 @@ namespace Godot [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class ScriptPathAttribute : Attribute { + /// <summary> + /// File path to the script. + /// </summary> public string Path { get; } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index bb1d6e1661..5d390a298d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -29,7 +29,7 @@ namespace Godot /// <value>Equivalent to <see cref="Column0"/> and array index <c>[0]</c>.</value> public Vector3 x { - get => Column0; + readonly get => Column0; set => Column0 = value; } @@ -39,7 +39,7 @@ namespace Godot /// <value>Equivalent to <see cref="Column1"/> and array index <c>[1]</c>.</value> public Vector3 y { - get => Column1; + readonly get => Column1; set => Column1 = value; } @@ -49,7 +49,7 @@ namespace Godot /// <value>Equivalent to <see cref="Column2"/> and array index <c>[2]</c>.</value> public Vector3 z { - get => Column2; + readonly get => Column2; set => Column2 = value; } @@ -80,7 +80,7 @@ namespace Godot /// <value>Equivalent to <see cref="x"/> and array index <c>[0]</c>.</value> public Vector3 Column0 { - get => new Vector3(Row0.x, Row1.x, Row2.x); + readonly get => new Vector3(Row0.x, Row1.x, Row2.x); set { Row0.x = value.x; @@ -95,7 +95,7 @@ namespace Godot /// <value>Equivalent to <see cref="y"/> and array index <c>[1]</c>.</value> public Vector3 Column1 { - get => new Vector3(Row0.y, Row1.y, Row2.y); + readonly get => new Vector3(Row0.y, Row1.y, Row2.y); set { Row0.y = value.x; @@ -110,7 +110,7 @@ namespace Godot /// <value>Equivalent to <see cref="z"/> and array index <c>[2]</c>.</value> public Vector3 Column2 { - get => new Vector3(Row0.z, Row1.z, Row2.z); + readonly get => new Vector3(Row0.z, Row1.z, Row2.z); set { Row0.z = value.x; @@ -125,7 +125,7 @@ namespace Godot /// <value>Equivalent to the lengths of each column vector, but negative if the determinant is negative.</value> public Vector3 Scale { - get + readonly get { real_t detSign = Mathf.Sign(Determinant()); return detSign * new Vector3 @@ -154,7 +154,7 @@ namespace Godot /// <value>The basis column.</value> public Vector3 this[int column] { - get + readonly get { switch (column) { @@ -195,7 +195,7 @@ namespace Godot /// <value>The matrix element.</value> public real_t this[int column, int row] { - get + readonly get { return this[column][row]; } @@ -234,7 +234,7 @@ namespace Godot /// and is usually considered invalid. /// </summary> /// <returns>The determinant of the basis matrix.</returns> - public real_t Determinant() + public readonly real_t Determinant() { real_t cofac00 = Row1[1] * Row2[2] - Row1[2] * Row2[1]; real_t cofac10 = Row1[2] * Row2[0] - Row1[0] * Row2[2]; @@ -255,7 +255,7 @@ namespace Godot /// </summary> /// <param name="order">The Euler order to use. By default, use YXZ order (most common).</param> /// <returns>A <see cref="Vector3"/> representing the basis rotation in Euler angles.</returns> - public Vector3 GetEuler(EulerOrder order = EulerOrder.Yxz) + public readonly Vector3 GetEuler(EulerOrder order = EulerOrder.Yxz) { switch (order) { @@ -499,7 +499,7 @@ namespace Godot /// mind that quaternions should generally be preferred to Euler angles. /// </summary> /// <returns>A <see cref="Quaternion"/> representing the basis's rotation.</returns> - internal Quaternion GetQuaternion() + internal readonly Quaternion GetQuaternion() { real_t trace = Row0[0] + Row1[1] + Row2[2]; @@ -558,7 +558,7 @@ namespace Godot /// be preferred to Euler angles. /// </summary> /// <returns>The basis rotation.</returns> - public Quaternion GetRotationQuaternion() + public readonly Quaternion GetRotationQuaternion() { Basis orthonormalizedBasis = Orthonormalized(); real_t det = orthonormalizedBasis.Determinant(); @@ -581,7 +581,7 @@ namespace Godot /// <paramref name="index"/> is not 0, 1 or 2. /// </exception> /// <returns>One of <c>Row0</c>, <c>Row1</c>, or <c>Row2</c>.</returns> - public Vector3 GetRow(int index) + public readonly Vector3 GetRow(int index) { switch (index) { @@ -633,7 +633,7 @@ namespace Godot /// For further details, refer to the Godot source code. /// </summary> /// <returns>The orthogonal index.</returns> - public int GetOrthogonalIndex() + public readonly int GetOrthogonalIndex() { var orth = this; @@ -679,7 +679,7 @@ namespace Godot /// Returns the inverse of the matrix. /// </summary> /// <returns>The inverse matrix.</returns> - public Basis Inverse() + public readonly Basis Inverse() { real_t cofac00 = Row1[1] * Row2[2] - Row1[2] * Row2[1]; real_t cofac10 = Row1[2] * Row2[0] - Row1[0] * Row2[2]; @@ -709,7 +709,7 @@ namespace Godot ); } - internal Basis Lerp(Basis to, real_t weight) + internal readonly Basis Lerp(Basis to, real_t weight) { Basis b = this; b.Row0 = Row0.Lerp(to.Row0, weight); @@ -724,7 +724,7 @@ namespace Godot /// This performs a Gram-Schmidt orthonormalization on the basis of the matrix. /// </summary> /// <returns>An orthonormalized basis matrix.</returns> - public Basis Orthonormalized() + public readonly Basis Orthonormalized() { Vector3 column0 = this[0]; Vector3 column1 = this[1]; @@ -746,7 +746,7 @@ namespace Godot /// <param name="axis">The axis to rotate around. Must be normalized.</param> /// <param name="angle">The angle to rotate, in radians.</param> /// <returns>The rotated basis matrix.</returns> - public Basis Rotated(Vector3 axis, real_t angle) + public readonly Basis Rotated(Vector3 axis, real_t angle) { return new Basis(axis, angle) * this; } @@ -756,7 +756,7 @@ namespace Godot /// </summary> /// <param name="scale">The scale to introduce.</param> /// <returns>The scaled basis matrix.</returns> - public Basis Scaled(Vector3 scale) + public readonly Basis Scaled(Vector3 scale) { Basis b = this; b.Row0 *= scale.x; @@ -772,7 +772,7 @@ namespace Godot /// <param name="target">The destination basis for interpolation.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The resulting basis matrix of the interpolation.</returns> - public Basis Slerp(Basis target, real_t weight) + public readonly Basis Slerp(Basis target, real_t weight) { Quaternion from = new Quaternion(this); Quaternion to = new Quaternion(target); @@ -790,7 +790,7 @@ namespace Godot /// </summary> /// <param name="with">A vector to calculate the dot product with.</param> /// <returns>The resulting dot product.</returns> - public real_t Tdotx(Vector3 with) + public readonly real_t Tdotx(Vector3 with) { return Row0[0] * with[0] + Row1[0] * with[1] + Row2[0] * with[2]; } @@ -800,7 +800,7 @@ namespace Godot /// </summary> /// <param name="with">A vector to calculate the dot product with.</param> /// <returns>The resulting dot product.</returns> - public real_t Tdoty(Vector3 with) + public readonly real_t Tdoty(Vector3 with) { return Row0[1] * with[0] + Row1[1] * with[1] + Row2[1] * with[2]; } @@ -810,7 +810,7 @@ namespace Godot /// </summary> /// <param name="with">A vector to calculate the dot product with.</param> /// <returns>The resulting dot product.</returns> - public real_t Tdotz(Vector3 with) + public readonly real_t Tdotz(Vector3 with) { return Row0[2] * with[0] + Row1[2] * with[1] + Row2[2] * with[2]; } @@ -819,21 +819,18 @@ namespace Godot /// Returns the transposed version of the basis matrix. /// </summary> /// <returns>The transposed basis matrix.</returns> - public Basis Transposed() + public readonly Basis Transposed() { Basis tr = this; - real_t temp = tr.Row0[1]; - tr.Row0[1] = tr.Row1[0]; - tr.Row1[0] = temp; + tr.Row0[1] = Row1[0]; + tr.Row1[0] = Row0[1]; - temp = tr.Row0[2]; - tr.Row0[2] = tr.Row2[0]; - tr.Row2[0] = temp; + tr.Row0[2] = Row2[0]; + tr.Row2[0] = Row0[2]; - temp = tr.Row1[2]; - tr.Row1[2] = tr.Row2[1]; - tr.Row2[1] = temp; + tr.Row1[2] = Row2[1]; + tr.Row2[1] = Row1[2]; return tr; } @@ -1121,7 +1118,7 @@ namespace Godot /// </summary> /// <param name="obj">The object to compare with.</param> /// <returns>Whether or not the basis matrix and the object are exactly equal.</returns> - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { return obj is Basis other && Equals(other); } @@ -1133,7 +1130,7 @@ namespace Godot /// </summary> /// <param name="other">The other basis.</param> /// <returns>Whether or not the basis matrices are exactly equal.</returns> - public bool Equals(Basis other) + public readonly bool Equals(Basis other) { return Row0.Equals(other.Row0) && Row1.Equals(other.Row1) && Row2.Equals(other.Row2); } @@ -1144,7 +1141,7 @@ namespace Godot /// </summary> /// <param name="other">The other basis to compare.</param> /// <returns>Whether or not the bases are approximately equal.</returns> - public bool IsEqualApprox(Basis other) + public readonly bool IsEqualApprox(Basis other) { return Row0.IsEqualApprox(other.Row0) && Row1.IsEqualApprox(other.Row1) && Row2.IsEqualApprox(other.Row2); } @@ -1153,7 +1150,7 @@ namespace Godot /// Serves as the hash function for <see cref="Basis"/>. /// </summary> /// <returns>A hash code for this basis.</returns> - public override int GetHashCode() + public override readonly int GetHashCode() { return Row0.GetHashCode() ^ Row1.GetHashCode() ^ Row2.GetHashCode(); } @@ -1162,7 +1159,7 @@ namespace Godot /// Converts this <see cref="Basis"/> to a string. /// </summary> /// <returns>A string representation of this basis.</returns> - public override string ToString() + public override readonly string ToString() { return $"[X: {x}, Y: {y}, Z: {z}]"; } @@ -1171,7 +1168,7 @@ namespace Godot /// Converts this <see cref="Basis"/> to a string with the given <paramref name="format"/>. /// </summary> /// <returns>A string representation of this basis.</returns> - public string ToString(string format) + public readonly string ToString(string format) { return $"[X: {x.ToString(format)}, Y: {y.ToString(format)}, Z: {z.ToString(format)}]"; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/MethodInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/MethodInfo.cs index 647ae436ff..85d38f9727 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/MethodInfo.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/MethodInfo.cs @@ -4,7 +4,7 @@ namespace Godot.Bridge; #nullable enable -public struct MethodInfo +public readonly struct MethodInfo { public StringName Name { get; init; } public PropertyInfo ReturnVal { get; init; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/PropertyInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/PropertyInfo.cs index 80d6f7b4a5..0f447b93c8 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/PropertyInfo.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/PropertyInfo.cs @@ -2,7 +2,7 @@ namespace Godot.Bridge; #nullable enable -public struct PropertyInfo +public readonly struct PropertyInfo { public Variant.Type Type { get; init; } public StringName Name { get; init; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs index d83cf43eb2..e6a8054ae2 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -159,7 +159,7 @@ namespace Godot.Bridge for (int i = 0; i < paramCount; i++) { - invokeParams[i] = Marshaling.ConvertVariantToManagedObjectOfType( + invokeParams[i] = DelegateUtils.RuntimeTypeConversionHelper.ConvertToObjectOfType( *args[i], parameters[i].ParameterType); } @@ -827,12 +827,13 @@ namespace Godot.Bridge { // Weird limitation, hence the need for aux: // "In the case of pointer types, you can use a stackalloc expression only in a local variable declaration to initialize the variable." - var aux = stackalloc godotsharp_property_info[length]; + var aux = stackalloc godotsharp_property_info[stackMaxLength]; interopProperties = aux; } else { - interopProperties = ((godotsharp_property_info*)NativeMemory.Alloc((nuint)length, (nuint)sizeof(godotsharp_property_info)))!; + interopProperties = ((godotsharp_property_info*)NativeMemory.Alloc( + (nuint)length, (nuint)sizeof(godotsharp_property_info)))!; } try @@ -858,8 +859,8 @@ namespace Godot.Bridge addPropInfoFunc(scriptPtr, ¤tClassName, interopProperties, length); - // We're borrowing the StringName's without making an owning copy, so the - // managed collection needs to be kept alive until `addPropInfoFunc` returns. + // We're borrowing the native value of the StringName entries. + // The dictionary needs to be kept alive until `addPropInfoFunc` returns. GC.KeepAlive(properties); } finally @@ -884,12 +885,7 @@ namespace Godot.Bridge { // Careful with padding... public godot_string_name Name; // Not owned - public godot_variant Value; - - public void Dispose() - { - Value.Dispose(); - } + public godot_variant Value; // Not owned } [UnmanagedCallersOnly] @@ -928,10 +924,35 @@ namespace Godot.Bridge if (getGodotPropertyDefaultValuesMethod == null) return; - var defaultValues = (Dictionary<StringName, object>?) - getGodotPropertyDefaultValuesMethod.Invoke(null, null); + var defaultValuesObj = getGodotPropertyDefaultValuesMethod.Invoke(null, null); + + if (defaultValuesObj == null) + return; + + Dictionary<StringName, Variant> defaultValues; + + if (defaultValuesObj is Dictionary<StringName, object> defaultValuesLegacy) + { + // We have to support this for some time, otherwise this could cause data loss for projects + // built with previous releases. Ideally, we should remove this before Godot 4.0 stable. + + if (defaultValuesLegacy.Count <= 0) + return; - if (defaultValues == null || defaultValues.Count <= 0) + defaultValues = new(); + + foreach (var pair in defaultValuesLegacy) + { + defaultValues[pair.Key] = Variant.CreateTakingOwnershipOfDisposableValue( + DelegateUtils.RuntimeTypeConversionHelper.ConvertToVariant(pair.Value)); + } + } + else + { + defaultValues = (Dictionary<StringName, Variant>)defaultValuesObj; + } + + if (defaultValues.Count <= 0) return; int length = defaultValues.Count; @@ -947,12 +968,13 @@ namespace Godot.Bridge { // Weird limitation, hence the need for aux: // "In the case of pointer types, you can use a stackalloc expression only in a local variable declaration to initialize the variable." - var aux = stackalloc godotsharp_property_def_val_pair[length]; + var aux = stackalloc godotsharp_property_def_val_pair[stackMaxLength]; interopDefaultValues = aux; } else { - interopDefaultValues = ((godotsharp_property_def_val_pair*)NativeMemory.Alloc((nuint)length, (nuint)sizeof(godotsharp_property_def_val_pair)))!; + interopDefaultValues = ((godotsharp_property_def_val_pair*)NativeMemory.Alloc( + (nuint)length, (nuint)sizeof(godotsharp_property_def_val_pair)))!; } try @@ -963,7 +985,7 @@ namespace Godot.Bridge godotsharp_property_def_val_pair interopProperty = new() { Name = (godot_string_name)defaultValuePair.Key.NativeValue, // Not owned - Value = Marshaling.ConvertManagedObjectToVariant(defaultValuePair.Value) + Value = (godot_variant)defaultValuePair.Value.NativeVar // Not owned }; interopDefaultValues[i] = interopProperty; @@ -973,15 +995,12 @@ namespace Godot.Bridge addDefValFunc(scriptPtr, interopDefaultValues, length); - // We're borrowing the StringName's without making an owning copy, so the - // managed collection needs to be kept alive until `addDefValFunc` returns. + // We're borrowing the native value of the StringName and Variant entries. + // The dictionary needs to be kept alive until `addDefValFunc` returns. GC.KeepAlive(defaultValues); } finally { - for (int i = 0; i < length; i++) - interopDefaultValues[i].Dispose(); - if (!useStack) NativeMemory.Free(interopDefaultValues); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs index f9309ca13e..23b0aa9204 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs @@ -77,7 +77,7 @@ namespace Godot _trampoline = trampoline; } - private const int VarArgsSpanThreshold = 5; + private const int VarArgsSpanThreshold = 10; /// <summary> /// Calls the method represented by this <see cref="Callable"/>. @@ -92,15 +92,13 @@ namespace Godot int argc = args.Length; Span<godot_variant.movable> argsStoreSpan = argc <= VarArgsSpanThreshold ? - stackalloc godot_variant.movable[VarArgsSpanThreshold].Cleared() : + stackalloc godot_variant.movable[VarArgsSpanThreshold] : new godot_variant.movable[argc]; - Span<IntPtr> argsSpan = argc <= 10 ? - stackalloc IntPtr[argc] : + Span<IntPtr> argsSpan = argc <= VarArgsSpanThreshold ? + stackalloc IntPtr[VarArgsSpanThreshold] : new IntPtr[argc]; - using var variantSpanDisposer = new VariantSpanDisposer(argsStoreSpan); - fixed (godot_variant* varargs = &MemoryMarshal.GetReference(argsStoreSpan).DangerousSelfRef) fixed (IntPtr* argsPtr = &MemoryMarshal.GetReference(argsSpan)) { @@ -128,15 +126,13 @@ namespace Godot int argc = args.Length; Span<godot_variant.movable> argsStoreSpan = argc <= VarArgsSpanThreshold ? - stackalloc godot_variant.movable[VarArgsSpanThreshold].Cleared() : + stackalloc godot_variant.movable[VarArgsSpanThreshold] : new godot_variant.movable[argc]; - Span<IntPtr> argsSpan = argc <= 10 ? - stackalloc IntPtr[argc] : + Span<IntPtr> argsSpan = argc <= VarArgsSpanThreshold ? + stackalloc IntPtr[VarArgsSpanThreshold] : new IntPtr[argc]; - using var variantSpanDisposer = new VariantSpanDisposer(argsStoreSpan); - fixed (godot_variant* varargs = &MemoryMarshal.GetReference(argsStoreSpan).DangerousSelfRef) fixed (IntPtr* argsPtr = &MemoryMarshal.GetReference(argsSpan)) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs index 6c6a104019..ff385da1c9 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs @@ -54,7 +54,7 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 1); ((Action<T0>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]) + VariantUtils.ConvertTo<T0>(args[0]) ); ret = default; @@ -73,8 +73,8 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 2); ((Action<T0, T1>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]) ); ret = default; @@ -93,9 +93,9 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 3); ((Action<T0, T1, T2>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]) ); ret = default; @@ -114,10 +114,10 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 4); ((Action<T0, T1, T2, T3>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]) ); ret = default; @@ -136,11 +136,11 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 5); ((Action<T0, T1, T2, T3, T4>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]) ); ret = default; @@ -159,12 +159,12 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 6); ((Action<T0, T1, T2, T3, T4, T5>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), - VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]), + VariantUtils.ConvertTo<T5>(args[5]) ); ret = default; @@ -183,13 +183,13 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 7); ((Action<T0, T1, T2, T3, T4, T5, T6>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), - VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]), - VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]), + VariantUtils.ConvertTo<T5>(args[5]), + VariantUtils.ConvertTo<T6>(args[6]) ); ret = default; @@ -208,14 +208,14 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 8); ((Action<T0, T1, T2, T3, T4, T5, T6, T7>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), - VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]), - VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]), - VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]), + VariantUtils.ConvertTo<T5>(args[5]), + VariantUtils.ConvertTo<T6>(args[6]), + VariantUtils.ConvertTo<T7>(args[7]) ); ret = default; @@ -234,15 +234,15 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 9); ((Action<T0, T1, T2, T3, T4, T5, T6, T7, T8>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), - VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]), - VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]), - VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7]), - VariantConversionCallbacks.GetToManagedCallback<T8>()(args[8]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]), + VariantUtils.ConvertTo<T5>(args[5]), + VariantUtils.ConvertTo<T6>(args[6]), + VariantUtils.ConvertTo<T7>(args[7]), + VariantUtils.ConvertTo<T8>(args[8]) ); ret = default; @@ -265,7 +265,7 @@ public readonly partial struct Callable TResult res = ((Func<TResult>)delegateObj)(); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); @@ -281,10 +281,10 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 1); TResult res = ((Func<T0, TResult>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]) + VariantUtils.ConvertTo<T0>(args[0]) ); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); @@ -300,11 +300,11 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 2); TResult res = ((Func<T0, T1, TResult>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]) ); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); @@ -320,12 +320,12 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 3); TResult res = ((Func<T0, T1, T2, TResult>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]) ); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); @@ -341,13 +341,13 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 4); TResult res = ((Func<T0, T1, T2, T3, TResult>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]) ); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); @@ -363,14 +363,14 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 5); TResult res = ((Func<T0, T1, T2, T3, T4, TResult>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]) ); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); @@ -386,15 +386,15 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 6); TResult res = ((Func<T0, T1, T2, T3, T4, T5, TResult>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), - VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]), + VariantUtils.ConvertTo<T5>(args[5]) ); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); @@ -410,16 +410,16 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 7); TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, TResult>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), - VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]), - VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]), + VariantUtils.ConvertTo<T5>(args[5]), + VariantUtils.ConvertTo<T6>(args[6]) ); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); @@ -435,17 +435,17 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 8); TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, T7, TResult>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), - VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]), - VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]), - VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]), + VariantUtils.ConvertTo<T5>(args[5]), + VariantUtils.ConvertTo<T6>(args[6]), + VariantUtils.ConvertTo<T7>(args[7]) ); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); @@ -461,18 +461,18 @@ public readonly partial struct Callable ThrowIfArgCountMismatch(args, 9); TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, T7, T8, TResult>)delegateObj)( - VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), - VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), - VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), - VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), - VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), - VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]), - VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]), - VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7]), - VariantConversionCallbacks.GetToManagedCallback<T8>()(args[8]) + VariantUtils.ConvertTo<T0>(args[0]), + VariantUtils.ConvertTo<T1>(args[1]), + VariantUtils.ConvertTo<T2>(args[2]), + VariantUtils.ConvertTo<T3>(args[3]), + VariantUtils.ConvertTo<T4>(args[4]), + VariantUtils.ConvertTo<T5>(args[5]), + VariantUtils.ConvertTo<T6>(args[6]), + VariantUtils.ConvertTo<T7>(args[7]), + VariantUtils.ConvertTo<T8>(args[8]) ); - ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + ret = VariantUtils.CreateFrom(res); } return CreateWithUnsafeTrampoline(func, &Trampoline); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs index ee9e59f9fa..f49023555b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs @@ -43,7 +43,7 @@ namespace Godot /// <value>Getting is equivalent to multiplying by 255 and rounding. Setting is equivalent to dividing by 255.</value> public int r8 { - get + readonly get { return (int)Math.Round(r * 255.0f); } @@ -59,7 +59,7 @@ namespace Godot /// <value>Getting is equivalent to multiplying by 255 and rounding. Setting is equivalent to dividing by 255.</value> public int g8 { - get + readonly get { return (int)Math.Round(g * 255.0f); } @@ -75,7 +75,7 @@ namespace Godot /// <value>Getting is equivalent to multiplying by 255 and rounding. Setting is equivalent to dividing by 255.</value> public int b8 { - get + readonly get { return (int)Math.Round(b * 255.0f); } @@ -91,7 +91,7 @@ namespace Godot /// <value>Getting is equivalent to multiplying by 255 and rounding. Setting is equivalent to dividing by 255.</value> public int a8 { - get + readonly get { return (int)Math.Round(a * 255.0f); } @@ -107,7 +107,7 @@ namespace Godot /// <value>Getting is a long process, refer to the source code for details. Setting uses <see cref="FromHSV"/>.</value> public float h { - get + readonly get { float max = Math.Max(r, Math.Max(g, b)); float min = Math.Min(r, Math.Min(g, b)); @@ -155,7 +155,7 @@ namespace Godot /// <value>Getting is equivalent to the ratio between the min and max RGB value. Setting uses <see cref="FromHSV"/>.</value> public float s { - get + readonly get { float max = Math.Max(r, Math.Max(g, b)); float min = Math.Min(r, Math.Min(g, b)); @@ -176,7 +176,7 @@ namespace Godot /// <value>Getting is equivalent to using <see cref="Math.Max(float, float)"/> on the RGB components. Setting uses <see cref="FromHSV"/>.</value> public float v { - get + readonly get { return Math.Max(r, Math.Max(g, b)); } @@ -197,7 +197,7 @@ namespace Godot /// </value> public float this[int index] { - get + readonly get { switch (index) { @@ -242,7 +242,7 @@ namespace Godot /// </summary> /// <param name="over">The color to blend over.</param> /// <returns>This color blended over <paramref name="over"/>.</returns> - public Color Blend(Color over) + public readonly Color Blend(Color over) { Color res; @@ -269,7 +269,7 @@ namespace Godot /// <param name="min">The color with minimum allowed values.</param> /// <param name="max">The color with maximum allowed values.</param> /// <returns>The color with all components clamped.</returns> - public Color Clamp(Color? min = null, Color? max = null) + public readonly Color Clamp(Color? min = null, Color? max = null) { Color minimum = min ?? new Color(0, 0, 0, 0); Color maximum = max ?? new Color(1, 1, 1, 1); @@ -288,7 +288,7 @@ namespace Godot /// </summary> /// <param name="amount">The ratio to darken by.</param> /// <returns>The darkened color.</returns> - public Color Darkened(float amount) + public readonly Color Darkened(float amount) { Color res = this; res.r *= 1.0f - amount; @@ -301,7 +301,7 @@ namespace Godot /// Returns the inverted color: <c>(1 - r, 1 - g, 1 - b, a)</c>. /// </summary> /// <returns>The inverted color.</returns> - public Color Inverted() + public readonly Color Inverted() { return new Color( 1.0f - r, @@ -317,7 +317,7 @@ namespace Godot /// </summary> /// <param name="amount">The ratio to lighten by.</param> /// <returns>The darkened color.</returns> - public Color Lightened(float amount) + public readonly Color Lightened(float amount) { Color res = this; res.r += (1.0f - res.r) * amount; @@ -333,7 +333,7 @@ namespace Godot /// <param name="to">The destination color for interpolation.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The resulting color of the interpolation.</returns> - public Color Lerp(Color to, real_t weight) + public readonly Color Lerp(Color to, real_t weight) { return new Color ( @@ -351,7 +351,7 @@ namespace Godot /// <param name="to">The destination color for interpolation.</param> /// <param name="weight">A color with components on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The resulting color of the interpolation.</returns> - public Color Lerp(Color to, Color weight) + public readonly Color Lerp(Color to, Color weight) { return new Color ( @@ -368,7 +368,7 @@ namespace Godot /// ABGR is the reversed version of the default format. /// </summary> /// <returns>A <see langword="uint"/> representing this color in ABGR32 format.</returns> - public uint ToAbgr32() + public readonly uint ToAbgr32() { uint c = (byte)Math.Round(a * 255); c <<= 8; @@ -387,7 +387,7 @@ namespace Godot /// ABGR is the reversed version of the default format. /// </summary> /// <returns>A <see langword="ulong"/> representing this color in ABGR64 format.</returns> - public ulong ToAbgr64() + public readonly ulong ToAbgr64() { ulong c = (ushort)Math.Round(a * 65535); c <<= 16; @@ -406,7 +406,7 @@ namespace Godot /// ARGB is more compatible with DirectX, but not used much in Godot. /// </summary> /// <returns>A <see langword="uint"/> representing this color in ARGB32 format.</returns> - public uint ToArgb32() + public readonly uint ToArgb32() { uint c = (byte)Math.Round(a * 255); c <<= 8; @@ -425,7 +425,7 @@ namespace Godot /// ARGB is more compatible with DirectX, but not used much in Godot. /// </summary> /// <returns>A <see langword="ulong"/> representing this color in ARGB64 format.</returns> - public ulong ToArgb64() + public readonly ulong ToArgb64() { ulong c = (ushort)Math.Round(a * 65535); c <<= 16; @@ -444,7 +444,7 @@ namespace Godot /// RGBA is Godot's default and recommended format. /// </summary> /// <returns>A <see langword="uint"/> representing this color in RGBA32 format.</returns> - public uint ToRgba32() + public readonly uint ToRgba32() { uint c = (byte)Math.Round(r * 255); c <<= 8; @@ -463,7 +463,7 @@ namespace Godot /// RGBA is Godot's default and recommended format. /// </summary> /// <returns>A <see langword="ulong"/> representing this color in RGBA64 format.</returns> - public ulong ToRgba64() + public readonly ulong ToRgba64() { ulong c = (ushort)Math.Round(r * 65535); c <<= 16; @@ -483,7 +483,7 @@ namespace Godot /// Whether or not to include alpha. If <see langword="false"/>, the color is RGB instead of RGBA. /// </param> /// <returns>A string for the HTML hexadecimal representation of this color.</returns> - public string ToHTML(bool includeAlpha = true) + public readonly string ToHTML(bool includeAlpha = true) { string txt = string.Empty; @@ -777,7 +777,7 @@ namespace Godot /// <param name="hue">Output parameter for the HSV hue.</param> /// <param name="saturation">Output parameter for the HSV saturation.</param> /// <param name="value">Output parameter for the HSV value.</param> - public void ToHSV(out float hue, out float saturation, out float value) + public readonly void ToHSV(out float hue, out float saturation, out float value) { float max = (float)Mathf.Max(r, Mathf.Max(g, b)); float min = (float)Mathf.Min(r, Mathf.Min(g, b)); @@ -1149,7 +1149,7 @@ namespace Godot /// </summary> /// <param name="obj">The other object to compare.</param> /// <returns>Whether or not the color and the other object are equal.</returns> - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { return obj is Color other && Equals(other); } @@ -1161,7 +1161,7 @@ namespace Godot /// </summary> /// <param name="other">The other color.</param> /// <returns>Whether or not the colors are equal.</returns> - public bool Equals(Color other) + public readonly bool Equals(Color other) { return r == other.r && g == other.g && b == other.b && a == other.a; } @@ -1172,7 +1172,7 @@ namespace Godot /// </summary> /// <param name="other">The other color to compare.</param> /// <returns>Whether or not the colors are approximately equal.</returns> - public bool IsEqualApprox(Color other) + public readonly bool IsEqualApprox(Color other) { return Mathf.IsEqualApprox(r, other.r) && Mathf.IsEqualApprox(g, other.g) && Mathf.IsEqualApprox(b, other.b) && Mathf.IsEqualApprox(a, other.a); } @@ -1181,7 +1181,7 @@ namespace Godot /// Serves as the hash function for <see cref="Color"/>. /// </summary> /// <returns>A hash code for this color.</returns> - public override int GetHashCode() + public override readonly int GetHashCode() { return r.GetHashCode() ^ g.GetHashCode() ^ b.GetHashCode() ^ a.GetHashCode(); } @@ -1190,7 +1190,7 @@ namespace Godot /// Converts this <see cref="Color"/> to a string. /// </summary> /// <returns>A string representation of this color.</returns> - public override string ToString() + public override readonly string ToString() { return $"({r}, {g}, {b}, {a})"; } @@ -1199,7 +1199,7 @@ namespace Godot /// Converts this <see cref="Color"/> to a string with the given <paramref name="format"/>. /// </summary> /// <returns>A string representation of this color.</returns> - public string ToString(string format) + public readonly string ToString(string format) { return $"({r.ToString(format)}, {g.ToString(format)}, {b.ToString(format)}, {a.ToString(format)})"; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs index d19e0c08f2..2a7a9e2026 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs @@ -186,7 +186,7 @@ namespace Godot writer.Write(field.Name); var fieldValue = field.GetValue(target); - using var fieldValueVariant = Marshaling.ConvertManagedObjectToVariant(fieldValue); + using var fieldValueVariant = RuntimeTypeConversionHelper.ConvertToVariant(fieldValue); byte[] valueBuffer = VarToBytes(fieldValueVariant); writer.Write(valueBuffer.Length); writer.Write(valueBuffer); @@ -443,7 +443,14 @@ namespace Godot FieldInfo? fieldInfo = targetType.GetField(name, BindingFlags.Instance | BindingFlags.Public); - fieldInfo?.SetValue(recreatedTarget, GD.BytesToVar(valueBuffer)); + + if (fieldInfo != null) + { + var variantValue = GD.BytesToVar(valueBuffer); + object? managedValue = RuntimeTypeConversionHelper.ConvertToObjectOfType( + (godot_variant)variantValue.NativeVar, fieldInfo.FieldType); + fieldInfo.SetValue(recreatedTarget, managedValue); + } } @delegate = Delegate.CreateDelegate(delegateType, recreatedTarget, methodInfo, @@ -537,5 +544,289 @@ namespace Godot return type; } + + internal static class RuntimeTypeConversionHelper + { + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + public static godot_variant ConvertToVariant(object? obj) + { + if (obj == null) + return default; + + switch (obj) + { + case bool @bool: + return VariantUtils.CreateFrom(@bool); + case char @char: + return VariantUtils.CreateFrom(@char); + case sbyte int8: + return VariantUtils.CreateFrom(int8); + case short int16: + return VariantUtils.CreateFrom(int16); + case int int32: + return VariantUtils.CreateFrom(int32); + case long int64: + return VariantUtils.CreateFrom(int64); + case byte uint8: + return VariantUtils.CreateFrom(uint8); + case ushort uint16: + return VariantUtils.CreateFrom(uint16); + case uint uint32: + return VariantUtils.CreateFrom(uint32); + case ulong uint64: + return VariantUtils.CreateFrom(uint64); + case float @float: + return VariantUtils.CreateFrom(@float); + case double @double: + return VariantUtils.CreateFrom(@double); + case Vector2 vector2: + return VariantUtils.CreateFrom(vector2); + case Vector2i vector2I: + return VariantUtils.CreateFrom(vector2I); + case Rect2 rect2: + return VariantUtils.CreateFrom(rect2); + case Rect2i rect2I: + return VariantUtils.CreateFrom(rect2I); + case Transform2D transform2D: + return VariantUtils.CreateFrom(transform2D); + case Vector3 vector3: + return VariantUtils.CreateFrom(vector3); + case Vector3i vector3I: + return VariantUtils.CreateFrom(vector3I); + case Vector4 vector4: + return VariantUtils.CreateFrom(vector4); + case Vector4i vector4I: + return VariantUtils.CreateFrom(vector4I); + case Basis basis: + return VariantUtils.CreateFrom(basis); + case Quaternion quaternion: + return VariantUtils.CreateFrom(quaternion); + case Transform3D transform3d: + return VariantUtils.CreateFrom(transform3d); + case Projection projection: + return VariantUtils.CreateFrom(projection); + case AABB aabb: + return VariantUtils.CreateFrom(aabb); + case Color color: + return VariantUtils.CreateFrom(color); + case Plane plane: + return VariantUtils.CreateFrom(plane); + case Callable callable: + return VariantUtils.CreateFrom(callable); + case SignalInfo signalInfo: + return VariantUtils.CreateFrom(signalInfo); + case string @string: + return VariantUtils.CreateFrom(@string); + case byte[] byteArray: + return VariantUtils.CreateFrom(byteArray); + case int[] int32Array: + return VariantUtils.CreateFrom(int32Array); + case long[] int64Array: + return VariantUtils.CreateFrom(int64Array); + case float[] floatArray: + return VariantUtils.CreateFrom(floatArray); + case double[] doubleArray: + return VariantUtils.CreateFrom(doubleArray); + case string[] stringArray: + return VariantUtils.CreateFrom(stringArray); + case Vector2[] vector2Array: + return VariantUtils.CreateFrom(vector2Array); + case Vector3[] vector3Array: + return VariantUtils.CreateFrom(vector3Array); + case Color[] colorArray: + return VariantUtils.CreateFrom(colorArray); + case StringName[] stringNameArray: + return VariantUtils.CreateFrom(stringNameArray); + case NodePath[] nodePathArray: + return VariantUtils.CreateFrom(nodePathArray); + case RID[] ridArray: + return VariantUtils.CreateFrom(ridArray); + case Godot.Object[] godotObjectArray: + return VariantUtils.CreateFrom(godotObjectArray); + case StringName stringName: + return VariantUtils.CreateFrom(stringName); + case NodePath nodePath: + return VariantUtils.CreateFrom(nodePath); + case RID rid: + return VariantUtils.CreateFrom(rid); + case Collections.Dictionary godotDictionary: + return VariantUtils.CreateFrom(godotDictionary); + case Collections.Array godotArray: + return VariantUtils.CreateFrom(godotArray); + case Variant variant: + return VariantUtils.CreateFrom(variant); + case Godot.Object godotObject: + return VariantUtils.CreateFrom(godotObject); + case Enum @enum: + return VariantUtils.CreateFrom(Convert.ToInt64(@enum)); + case Collections.IGenericGodotDictionary godotDictionary: + return VariantUtils.CreateFrom(godotDictionary.UnderlyingDictionary); + case Collections.IGenericGodotArray godotArray: + return VariantUtils.CreateFrom(godotArray.UnderlyingArray); + } + + GD.PushError("Attempted to convert an unmarshallable managed type to Variant. Name: '" + + obj.GetType().FullName + "."); + return new godot_variant(); + } + + private delegate object? ConvertToSystemObjectFunc(in godot_variant managed); + + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + // ReSharper disable once RedundantNameQualifier + private static readonly System.Collections.Generic.Dictionary<Type, ConvertToSystemObjectFunc> + ToSystemObjectFuncByType = new() + { + [typeof(bool)] = (in godot_variant variant) => VariantUtils.ConvertTo<bool>(variant), + [typeof(char)] = (in godot_variant variant) => VariantUtils.ConvertTo<char>(variant), + [typeof(sbyte)] = (in godot_variant variant) => VariantUtils.ConvertTo<sbyte>(variant), + [typeof(short)] = (in godot_variant variant) => VariantUtils.ConvertTo<short>(variant), + [typeof(int)] = (in godot_variant variant) => VariantUtils.ConvertTo<int>(variant), + [typeof(long)] = (in godot_variant variant) => VariantUtils.ConvertTo<long>(variant), + [typeof(byte)] = (in godot_variant variant) => VariantUtils.ConvertTo<byte>(variant), + [typeof(ushort)] = (in godot_variant variant) => VariantUtils.ConvertTo<ushort>(variant), + [typeof(uint)] = (in godot_variant variant) => VariantUtils.ConvertTo<uint>(variant), + [typeof(ulong)] = (in godot_variant variant) => VariantUtils.ConvertTo<ulong>(variant), + [typeof(float)] = (in godot_variant variant) => VariantUtils.ConvertTo<float>(variant), + [typeof(double)] = (in godot_variant variant) => VariantUtils.ConvertTo<double>(variant), + [typeof(Vector2)] = (in godot_variant variant) => VariantUtils.ConvertTo<Vector2>(variant), + [typeof(Vector2i)] = (in godot_variant variant) => VariantUtils.ConvertTo<Vector2i>(variant), + [typeof(Rect2)] = (in godot_variant variant) => VariantUtils.ConvertTo<Rect2>(variant), + [typeof(Rect2i)] = (in godot_variant variant) => VariantUtils.ConvertTo<Rect2i>(variant), + [typeof(Transform2D)] = (in godot_variant variant) => VariantUtils.ConvertTo<Transform2D>(variant), + [typeof(Vector3)] = (in godot_variant variant) => VariantUtils.ConvertTo<Vector3>(variant), + [typeof(Vector3i)] = (in godot_variant variant) => VariantUtils.ConvertTo<Vector3i>(variant), + [typeof(Basis)] = (in godot_variant variant) => VariantUtils.ConvertTo<Basis>(variant), + [typeof(Quaternion)] = (in godot_variant variant) => VariantUtils.ConvertTo<Quaternion>(variant), + [typeof(Transform3D)] = (in godot_variant variant) => VariantUtils.ConvertTo<Transform3D>(variant), + [typeof(Vector4)] = (in godot_variant variant) => VariantUtils.ConvertTo<Vector4>(variant), + [typeof(Vector4i)] = (in godot_variant variant) => VariantUtils.ConvertTo<Vector4i>(variant), + [typeof(AABB)] = (in godot_variant variant) => VariantUtils.ConvertTo<AABB>(variant), + [typeof(Color)] = (in godot_variant variant) => VariantUtils.ConvertTo<Color>(variant), + [typeof(Plane)] = (in godot_variant variant) => VariantUtils.ConvertTo<Plane>(variant), + [typeof(Callable)] = (in godot_variant variant) => VariantUtils.ConvertTo<Callable>(variant), + [typeof(SignalInfo)] = (in godot_variant variant) => VariantUtils.ConvertTo<SignalInfo>(variant), + [typeof(string)] = (in godot_variant variant) => VariantUtils.ConvertTo<string>(variant), + [typeof(byte[])] = (in godot_variant variant) => VariantUtils.ConvertTo<byte[]>(variant), + [typeof(int[])] = (in godot_variant variant) => VariantUtils.ConvertTo<int[]>(variant), + [typeof(long[])] = (in godot_variant variant) => VariantUtils.ConvertTo<long[]>(variant), + [typeof(float[])] = (in godot_variant variant) => VariantUtils.ConvertTo<float[]>(variant), + [typeof(double[])] = (in godot_variant variant) => VariantUtils.ConvertTo<double[]>(variant), + [typeof(string[])] = (in godot_variant variant) => VariantUtils.ConvertTo<string[]>(variant), + [typeof(Vector2[])] = (in godot_variant variant) => VariantUtils.ConvertTo<Vector2[]>(variant), + [typeof(Vector3[])] = (in godot_variant variant) => VariantUtils.ConvertTo<Vector3[]>(variant), + [typeof(Color[])] = (in godot_variant variant) => VariantUtils.ConvertTo<Color[]>(variant), + [typeof(StringName[])] = + (in godot_variant variant) => VariantUtils.ConvertTo<StringName[]>(variant), + [typeof(NodePath[])] = (in godot_variant variant) => VariantUtils.ConvertTo<NodePath[]>(variant), + [typeof(RID[])] = (in godot_variant variant) => VariantUtils.ConvertTo<RID[]>(variant), + [typeof(StringName)] = (in godot_variant variant) => VariantUtils.ConvertTo<StringName>(variant), + [typeof(NodePath)] = (in godot_variant variant) => VariantUtils.ConvertTo<NodePath>(variant), + [typeof(RID)] = (in godot_variant variant) => VariantUtils.ConvertTo<RID>(variant), + [typeof(Godot.Collections.Dictionary)] = (in godot_variant variant) => + VariantUtils.ConvertTo<Godot.Collections.Dictionary>(variant), + [typeof(Godot.Collections.Array)] = + (in godot_variant variant) => VariantUtils.ConvertTo<Godot.Collections.Array>(variant), + [typeof(Variant)] = (in godot_variant variant) => VariantUtils.ConvertTo<Variant>(variant), + }; + + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + public static object? ConvertToObjectOfType(in godot_variant variant, Type type) + { + if (ToSystemObjectFuncByType.TryGetValue(type, out var func)) + return func(variant); + + if (typeof(Godot.Object).IsAssignableFrom(type)) + return Convert.ChangeType(VariantUtils.ConvertTo<Godot.Object>(variant), type); + + if (typeof(Godot.Object[]).IsAssignableFrom(type)) + { + static Godot.Object[] ConvertToSystemArrayOfGodotObject(in godot_array nativeArray, Type type) + { + var array = Collections.Array.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_array_new_copy(nativeArray)); + + int length = array.Count; + var ret = (Godot.Object[])Activator.CreateInstance(type, length)!; + + for (int i = 0; i < length; i++) + ret[i] = array[i].AsGodotObject(); + + return ret; + } + + using var godotArray = NativeFuncs.godotsharp_variant_as_array(variant); + return Convert.ChangeType(ConvertToSystemArrayOfGodotObject(godotArray, type), type); + } + + if (type.IsEnum) + { + var enumUnderlyingType = type.GetEnumUnderlyingType(); + + switch (Type.GetTypeCode(enumUnderlyingType)) + { + case TypeCode.SByte: + return Enum.ToObject(type, VariantUtils.ConvertToInt8(variant)); + case TypeCode.Int16: + return Enum.ToObject(type, VariantUtils.ConvertToInt16(variant)); + case TypeCode.Int32: + return Enum.ToObject(type, VariantUtils.ConvertToInt32(variant)); + case TypeCode.Int64: + return Enum.ToObject(type, VariantUtils.ConvertToInt64(variant)); + case TypeCode.Byte: + return Enum.ToObject(type, VariantUtils.ConvertToUInt8(variant)); + case TypeCode.UInt16: + return Enum.ToObject(type, VariantUtils.ConvertToUInt16(variant)); + case TypeCode.UInt32: + return Enum.ToObject(type, VariantUtils.ConvertToUInt32(variant)); + case TypeCode.UInt64: + return Enum.ToObject(type, VariantUtils.ConvertToUInt64(variant)); + default: + { + GD.PushError( + "Attempted to convert Variant to enum value of unsupported underlying type. Name: " + + type.FullName + " : " + enumUnderlyingType.FullName + "."); + return null; + } + } + } + + if (type.IsGenericType) + { + var genericTypeDef = type.GetGenericTypeDefinition(); + + if (genericTypeDef == typeof(Godot.Collections.Dictionary<,>)) + { + var ctor = type.GetConstructor(BindingFlags.Default, + new[] { typeof(Godot.Collections.Dictionary) }); + + if (ctor == null) + throw new InvalidOperationException("Dictionary constructor not found"); + + return ctor.Invoke(new object?[] + { + VariantUtils.ConvertTo<Godot.Collections.Dictionary>(variant) + }); + } + + if (genericTypeDef == typeof(Godot.Collections.Array<>)) + { + var ctor = type.GetConstructor(BindingFlags.Default, + new[] { typeof(Godot.Collections.Array) }); + + if (ctor == null) + throw new InvalidOperationException("Array constructor not found"); + + return ctor.Invoke(new object?[] + { + VariantUtils.ConvertTo<Godot.Collections.Array>(variant) + }); + } + } + + GD.PushError($"Attempted to convert Variant to unsupported type. Name: {type.FullName}."); + return null; + } + } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs index f8793332a0..cf25e1f0ae 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs @@ -344,6 +344,11 @@ namespace Godot.Collections } } + internal interface IGenericGodotDictionary + { + public Dictionary UnderlyingDictionary { get; } + } + /// <summary> /// Typed wrapper around Godot's Dictionary class, a dictionary of Variant /// typed elements allocated in the engine in C++. Useful when @@ -354,7 +359,8 @@ namespace Godot.Collections /// <typeparam name="TValue">The type of the dictionary's values.</typeparam> public class Dictionary<[MustBeVariant] TKey, [MustBeVariant] TValue> : IDictionary<TKey, TValue>, - IReadOnlyDictionary<TKey, TValue> + IReadOnlyDictionary<TKey, TValue>, + IGenericGodotDictionary { private static godot_variant ToVariantFunc(in Dictionary<TKey, TValue> godotDictionary) => VariantUtils.CreateFromDictionary(godotDictionary); @@ -362,49 +368,16 @@ namespace Godot.Collections private static Dictionary<TKey, TValue> FromVariantFunc(in godot_variant variant) => VariantUtils.ConvertToDictionaryObject<TKey, TValue>(variant); - // ReSharper disable StaticMemberInGenericType - // Warning is about unique static fields being created for each generic type combination: - // https://www.jetbrains.com/help/resharper/StaticMemberInGenericType.html - // In our case this is exactly what we want. - - private static readonly unsafe delegate* managed<in TKey, godot_variant> ConvertKeyToVariantCallback; - private static readonly unsafe delegate* managed<in godot_variant, TKey> ConvertKeyToManagedCallback; - private static readonly unsafe delegate* managed<in TValue, godot_variant> ConvertValueToVariantCallback; - private static readonly unsafe delegate* managed<in godot_variant, TValue> ConvertValueToManagedCallback; - - // ReSharper restore StaticMemberInGenericType - static unsafe Dictionary() { - VariantConversionCallbacks.GenericConversionCallbacks[typeof(Dictionary<TKey, TValue>)] = - ( - (IntPtr)(delegate* managed<in Dictionary<TKey, TValue>, godot_variant>)&ToVariantFunc, - (IntPtr)(delegate* managed<in godot_variant, Dictionary<TKey, TValue>>)&FromVariantFunc - ); - - ConvertKeyToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TKey>(); - ConvertKeyToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TKey>(); - ConvertValueToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TValue>(); - ConvertValueToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TValue>(); - } - - private static unsafe void ValidateVariantConversionCallbacks() - { - if (ConvertKeyToVariantCallback == null || ConvertKeyToManagedCallback == null) - { - throw new InvalidOperationException( - $"The dictionary key type is not supported for conversion to Variant: '{typeof(TKey).FullName}'."); - } - - if (ConvertValueToVariantCallback == null || ConvertValueToManagedCallback == null) - { - throw new InvalidOperationException( - $"The dictionary value type is not supported for conversion to Variant: '{typeof(TValue).FullName}'."); - } + VariantUtils.GenericConversion<Dictionary<TKey, TValue>>.ToVariantCb = &ToVariantFunc; + VariantUtils.GenericConversion<Dictionary<TKey, TValue>>.FromVariantCb = &FromVariantFunc; } private readonly Dictionary _underlyingDict; + Dictionary IGenericGodotDictionary.UnderlyingDictionary => _underlyingDict; + internal ref godot_dictionary.movable NativeValue { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -416,8 +389,6 @@ namespace Godot.Collections /// </summary> public Dictionary() { - ValidateVariantConversionCallbacks(); - _underlyingDict = new Dictionary(); } @@ -428,8 +399,6 @@ namespace Godot.Collections /// <returns>A new Godot Dictionary.</returns> public Dictionary(IDictionary<TKey, TValue> dictionary) { - ValidateVariantConversionCallbacks(); - if (dictionary == null) throw new ArgumentNullException(nameof(dictionary)); @@ -446,8 +415,6 @@ namespace Godot.Collections /// <returns>A new Godot Dictionary.</returns> public Dictionary(Dictionary dictionary) { - ValidateVariantConversionCallbacks(); - _underlyingDict = dictionary; } @@ -481,18 +448,18 @@ namespace Godot.Collections /// Returns the value at the given <paramref name="key"/>. /// </summary> /// <value>The value at the given <paramref name="key"/>.</value> - public unsafe TValue this[TKey key] + public TValue this[TKey key] { get { - using var variantKey = ConvertKeyToVariantCallback(key); + using var variantKey = VariantUtils.CreateFrom(key); var self = (godot_dictionary)_underlyingDict.NativeValue; if (NativeFuncs.godotsharp_dictionary_try_get_value(ref self, variantKey, out godot_variant value).ToBool()) { using (value) - return ConvertValueToManagedCallback(value); + return VariantUtils.ConvertTo<TValue>(value); } else { @@ -501,8 +468,8 @@ namespace Godot.Collections } set { - using var variantKey = ConvertKeyToVariantCallback(key); - using var variantValue = ConvertValueToVariantCallback(value); + using var variantKey = VariantUtils.CreateFrom(key); + using var variantValue = VariantUtils.CreateFrom(value); var self = (godot_dictionary)_underlyingDict.NativeValue; NativeFuncs.godotsharp_dictionary_set_value(ref self, variantKey, variantValue); @@ -541,7 +508,7 @@ namespace Godot.Collections IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values; - private unsafe KeyValuePair<TKey, TValue> GetKeyValuePair(int index) + private KeyValuePair<TKey, TValue> GetKeyValuePair(int index) { var self = (godot_dictionary)_underlyingDict.NativeValue; NativeFuncs.godotsharp_dictionary_key_value_pair_at(ref self, index, @@ -551,8 +518,8 @@ namespace Godot.Collections using (value) { return new KeyValuePair<TKey, TValue>( - ConvertKeyToManagedCallback(key), - ConvertValueToManagedCallback(value)); + VariantUtils.ConvertTo<TKey>(key), + VariantUtils.ConvertTo<TValue>(value)); } } @@ -562,15 +529,15 @@ namespace Godot.Collections /// </summary> /// <param name="key">The key at which to add the object.</param> /// <param name="value">The object to add.</param> - public unsafe void Add(TKey key, TValue value) + public void Add(TKey key, TValue value) { - using var variantKey = ConvertKeyToVariantCallback(key); + using var variantKey = VariantUtils.CreateFrom(key); var self = (godot_dictionary)_underlyingDict.NativeValue; if (NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool()) throw new ArgumentException("An element with the same key already exists.", nameof(key)); - using var variantValue = ConvertValueToVariantCallback(value); + using var variantValue = VariantUtils.CreateFrom(value); NativeFuncs.godotsharp_dictionary_add(ref self, variantKey, variantValue); } @@ -579,9 +546,9 @@ namespace Godot.Collections /// </summary> /// <param name="key">The key to look for.</param> /// <returns>Whether or not this dictionary contains the given key.</returns> - public unsafe bool ContainsKey(TKey key) + public bool ContainsKey(TKey key) { - using var variantKey = ConvertKeyToVariantCallback(key); + using var variantKey = VariantUtils.CreateFrom(key); var self = (godot_dictionary)_underlyingDict.NativeValue; return NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool(); } @@ -590,9 +557,9 @@ namespace Godot.Collections /// Removes an element from this <see cref="Dictionary{TKey, TValue}"/> by key. /// </summary> /// <param name="key">The key of the element to remove.</param> - public unsafe bool Remove(TKey key) + public bool Remove(TKey key) { - using var variantKey = ConvertKeyToVariantCallback(key); + using var variantKey = VariantUtils.CreateFrom(key); var self = (godot_dictionary)_underlyingDict.NativeValue; return NativeFuncs.godotsharp_dictionary_remove_key(ref self, variantKey).ToBool(); } @@ -603,15 +570,15 @@ namespace Godot.Collections /// <param name="key">The key of the element to get.</param> /// <param name="value">The value at the given <paramref name="key"/>.</param> /// <returns>If an object was found for the given <paramref name="key"/>.</returns> - public unsafe bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) { - using var variantKey = ConvertKeyToVariantCallback(key); + using var variantKey = VariantUtils.CreateFrom(key); var self = (godot_dictionary)_underlyingDict.NativeValue; bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, variantKey, out godot_variant retValue).ToBool(); using (retValue) - value = found ? ConvertValueToManagedCallback(retValue) : default; + value = found ? VariantUtils.ConvertTo<TValue>(retValue) : default; return found; } @@ -635,9 +602,9 @@ namespace Godot.Collections /// </summary> public void Clear() => _underlyingDict.Clear(); - unsafe bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) + bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) { - using var variantKey = ConvertKeyToVariantCallback(item.Key); + using var variantKey = VariantUtils.CreateFrom(item.Key); var self = (godot_dictionary)_underlyingDict.NativeValue; bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, variantKey, out godot_variant retValue).ToBool(); @@ -647,7 +614,7 @@ namespace Godot.Collections if (!found) return false; - using var variantValue = ConvertValueToVariantCallback(item.Value); + using var variantValue = VariantUtils.CreateFrom(item.Value); return NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool(); } } @@ -680,9 +647,9 @@ namespace Godot.Collections } } - unsafe bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) + bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) { - using var variantKey = ConvertKeyToVariantCallback(item.Key); + using var variantKey = VariantUtils.CreateFrom(item.Key); var self = (godot_dictionary)_underlyingDict.NativeValue; bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, variantKey, out godot_variant retValue).ToBool(); @@ -692,7 +659,7 @@ namespace Godot.Collections if (!found) return false; - using var variantValue = ConvertValueToVariantCallback(item.Value); + using var variantValue = VariantUtils.CreateFrom(item.Value); if (NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool()) { return NativeFuncs.godotsharp_dictionary_remove_key( diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs index f2667c6807..3f9e986f62 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs @@ -282,7 +282,7 @@ namespace Godot /// <summary> /// Returns the point at the given <paramref name="t"/> on a one-dimensional Bezier curve defined by - /// the given <paramref name="control1"/>, <paramref name="control2"/> and <paramref name="end"/> points. + /// the given <paramref name="control1"/>, <paramref name="control2"/>, and <paramref name="end"/> points. /// </summary> /// <param name="start">The start value for the interpolation.</param> /// <param name="control1">Control point that defines the bezier curve.</param> @@ -303,6 +303,27 @@ namespace Godot } /// <summary> + /// Returns the derivative at the given <paramref name="t"/> on a one dimensional Bezier curve defined by + /// the given <paramref name="control1"/>, <paramref name="control2"/>, and <paramref name="end"/> points. + /// </summary> + /// <param name="start">The start value for the interpolation.</param> + /// <param name="control1">Control point that defines the bezier curve.</param> + /// <param name="control2">Control point that defines the bezier curve.</param> + /// <param name="end">The destination value for the interpolation.</param> + /// <param name="t">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting value of the interpolation.</returns> + public static real_t BezierDerivative(real_t start, real_t control1, real_t control2, real_t end, real_t t) + { + // Formula from Wikipedia article on Bezier curves + real_t omt = 1 - t; + real_t omt2 = omt * omt; + real_t t2 = t * t; + + real_t d = (control1 - start) * 3 * omt2 + (control2 - control1) * 6 * omt * t + (end - control2) * 3 * t2; + return d; + } + + /// <summary> /// Converts an angle expressed in degrees to radians. /// </summary> /// <param name="deg">An angle expressed in degrees.</param> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs index 649661ee06..ab3d3ef60f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs @@ -1,5 +1,7 @@ using System; using System.Runtime.InteropServices; +using Godot.Collections; +using Array = System.Array; // ReSharper disable InconsistentNaming @@ -148,6 +150,15 @@ namespace Godot.NativeInterop { if (typeof(Godot.Object).IsAssignableFrom(type)) return Variant.Type.Object; + + // We use `IsAssignableFrom` with our helper interfaces to detect generic Godot collections + // because `GetGenericTypeDefinition` is not supported in NativeAOT reflection-free mode. + + if (typeof(IGenericGodotDictionary).IsAssignableFrom(type)) + return Variant.Type.Dictionary; + + if (typeof(IGenericGodotArray).IsAssignableFrom(type)) + return Variant.Type.Array; } else if (type == typeof(Variant)) { @@ -183,508 +194,6 @@ namespace Godot.NativeInterop return Variant.Type.Nil; } - /* TODO: Reflection and type checking each time is slow. This will be replaced with source generators. */ - public static godot_variant ConvertManagedObjectToVariant(object? p_obj) - { - if (p_obj == null) - return new godot_variant(); - - switch (p_obj) - { - case bool @bool: - return VariantUtils.CreateFromBool(@bool); - case char @char: - return VariantUtils.CreateFromInt(@char); - case sbyte @int8: - return VariantUtils.CreateFromInt(@int8); - case short @int16: - return VariantUtils.CreateFromInt(@int16); - case int @int32: - return VariantUtils.CreateFromInt(@int32); - case long @int64: - return VariantUtils.CreateFromInt(@int64); - case byte @uint8: - return VariantUtils.CreateFromInt(@uint8); - case ushort @uint16: - return VariantUtils.CreateFromInt(@uint16); - case uint @uint32: - return VariantUtils.CreateFromInt(@uint32); - case ulong @uint64: - return VariantUtils.CreateFromInt(@uint64); - case float @float: - return VariantUtils.CreateFromFloat(@float); - case double @double: - return VariantUtils.CreateFromFloat(@double); - case Vector2 @vector2: - return VariantUtils.CreateFromVector2(@vector2); - case Vector2i @vector2i: - return VariantUtils.CreateFromVector2i(@vector2i); - case Rect2 @rect2: - return VariantUtils.CreateFromRect2(@rect2); - case Rect2i @rect2i: - return VariantUtils.CreateFromRect2i(@rect2i); - case Transform2D @transform2D: - return VariantUtils.CreateFromTransform2D(@transform2D); - case Vector3 @vector3: - return VariantUtils.CreateFromVector3(@vector3); - case Vector3i @vector3i: - return VariantUtils.CreateFromVector3i(@vector3i); - case Vector4 @vector4: - return VariantUtils.CreateFromVector4(@vector4); - case Vector4i @vector4i: - return VariantUtils.CreateFromVector4i(@vector4i); - case Basis @basis: - return VariantUtils.CreateFromBasis(@basis); - case Quaternion @quaternion: - return VariantUtils.CreateFromQuaternion(@quaternion); - case Transform3D @transform3d: - return VariantUtils.CreateFromTransform3D(@transform3d); - case Projection @projection: - return VariantUtils.CreateFromProjection(@projection); - case AABB @aabb: - return VariantUtils.CreateFromAABB(@aabb); - case Color @color: - return VariantUtils.CreateFromColor(@color); - case Plane @plane: - return VariantUtils.CreateFromPlane(@plane); - case Callable @callable: - return VariantUtils.CreateFromCallable(@callable); - case SignalInfo @signalInfo: - return VariantUtils.CreateFromSignalInfo(@signalInfo); - case Enum @enum: - return VariantUtils.CreateFromInt(Convert.ToInt64(@enum)); - case string @string: - return VariantUtils.CreateFromString(@string); - case byte[] byteArray: - return VariantUtils.CreateFromPackedByteArray(byteArray); - case int[] int32Array: - return VariantUtils.CreateFromPackedInt32Array(int32Array); - case long[] int64Array: - return VariantUtils.CreateFromPackedInt64Array(int64Array); - case float[] floatArray: - return VariantUtils.CreateFromPackedFloat32Array(floatArray); - case double[] doubleArray: - return VariantUtils.CreateFromPackedFloat64Array(doubleArray); - case string[] stringArray: - return VariantUtils.CreateFromPackedStringArray(stringArray); - case Vector2[] vector2Array: - return VariantUtils.CreateFromPackedVector2Array(vector2Array); - case Vector3[] vector3Array: - return VariantUtils.CreateFromPackedVector3Array(vector3Array); - case Color[] colorArray: - return VariantUtils.CreateFromPackedColorArray(colorArray); - case StringName[] stringNameArray: - return VariantUtils.CreateFromSystemArrayOfStringName(stringNameArray); - case NodePath[] nodePathArray: - return VariantUtils.CreateFromSystemArrayOfNodePath(nodePathArray); - case RID[] ridArray: - return VariantUtils.CreateFromSystemArrayOfRID(ridArray); - case Godot.Object[] godotObjectArray: - return VariantUtils.CreateFromSystemArrayOfGodotObject(godotObjectArray); - case Godot.Object godotObject: - return VariantUtils.CreateFromGodotObject(godotObject); - case StringName stringName: - return VariantUtils.CreateFromStringName(stringName); - case NodePath nodePath: - return VariantUtils.CreateFromNodePath(nodePath); - case RID rid: - return VariantUtils.CreateFromRID(rid); - case Collections.Dictionary godotDictionary: - return VariantUtils.CreateFromDictionary(godotDictionary); - case Collections.Array godotArray: - return VariantUtils.CreateFromArray(godotArray); - case Variant variant: - return NativeFuncs.godotsharp_variant_new_copy((godot_variant)variant.NativeVar); - } - - GD.PushError("Attempted to convert an unmarshallable managed type to Variant. Name: '" + - p_obj.GetType().FullName + "."); - return new godot_variant(); - } - - public static object? ConvertVariantToManagedObjectOfType(in godot_variant p_var, Type type) - { - // This function is only needed to set the value of properties. Fields have their own implementation, set_value_from_variant. - switch (Type.GetTypeCode(type)) - { - case TypeCode.Boolean: - return VariantUtils.ConvertToBool(p_var); - case TypeCode.Char: - return VariantUtils.ConvertToChar(p_var); - case TypeCode.SByte: - return VariantUtils.ConvertToInt8(p_var); - case TypeCode.Int16: - return VariantUtils.ConvertToInt16(p_var); - case TypeCode.Int32: - return VariantUtils.ConvertToInt32(p_var); - case TypeCode.Int64: - return VariantUtils.ConvertToInt64(p_var); - case TypeCode.Byte: - return VariantUtils.ConvertToUInt8(p_var); - case TypeCode.UInt16: - return VariantUtils.ConvertToUInt16(p_var); - case TypeCode.UInt32: - return VariantUtils.ConvertToUInt32(p_var); - case TypeCode.UInt64: - return VariantUtils.ConvertToUInt64(p_var); - case TypeCode.Single: - return VariantUtils.ConvertToFloat32(p_var); - case TypeCode.Double: - return VariantUtils.ConvertToFloat64(p_var); - case TypeCode.String: - return VariantUtils.ConvertToStringObject(p_var); - default: - { - if (type == typeof(Vector2)) - return VariantUtils.ConvertToVector2(p_var); - - if (type == typeof(Vector2i)) - return VariantUtils.ConvertToVector2i(p_var); - - if (type == typeof(Rect2)) - return VariantUtils.ConvertToRect2(p_var); - - if (type == typeof(Rect2i)) - return VariantUtils.ConvertToRect2i(p_var); - - if (type == typeof(Transform2D)) - return VariantUtils.ConvertToTransform2D(p_var); - - if (type == typeof(Vector3)) - return VariantUtils.ConvertToVector3(p_var); - - if (type == typeof(Vector3i)) - return VariantUtils.ConvertToVector3i(p_var); - - if (type == typeof(Vector4)) - return VariantUtils.ConvertToVector4(p_var); - - if (type == typeof(Vector4i)) - return VariantUtils.ConvertToVector4i(p_var); - - if (type == typeof(Basis)) - return VariantUtils.ConvertToBasis(p_var); - - if (type == typeof(Quaternion)) - return VariantUtils.ConvertToQuaternion(p_var); - - if (type == typeof(Transform3D)) - return VariantUtils.ConvertToTransform3D(p_var); - - if (type == typeof(Projection)) - return VariantUtils.ConvertToProjection(p_var); - - if (type == typeof(AABB)) - return VariantUtils.ConvertToAABB(p_var); - - if (type == typeof(Color)) - return VariantUtils.ConvertToColor(p_var); - - if (type == typeof(Plane)) - return VariantUtils.ConvertToPlane(p_var); - - if (type == typeof(Callable)) - return VariantUtils.ConvertToCallableManaged(p_var); - - if (type == typeof(SignalInfo)) - return VariantUtils.ConvertToSignalInfo(p_var); - - if (type.IsEnum) - { - var enumUnderlyingType = type.GetEnumUnderlyingType(); - switch (Type.GetTypeCode(enumUnderlyingType)) - { - case TypeCode.SByte: - return VariantUtils.ConvertToInt8(p_var); - case TypeCode.Int16: - return VariantUtils.ConvertToInt16(p_var); - case TypeCode.Int32: - return VariantUtils.ConvertToInt32(p_var); - case TypeCode.Int64: - return VariantUtils.ConvertToInt64(p_var); - case TypeCode.Byte: - return VariantUtils.ConvertToUInt8(p_var); - case TypeCode.UInt16: - return VariantUtils.ConvertToUInt16(p_var); - case TypeCode.UInt32: - return VariantUtils.ConvertToUInt32(p_var); - case TypeCode.UInt64: - return VariantUtils.ConvertToUInt64(p_var); - default: - { - GD.PushError( - "Attempted to convert Variant to enum value of unsupported underlying type. Name: " + - type.FullName + " : " + enumUnderlyingType.FullName + "."); - return null; - } - } - } - - if (type.IsArray || type.IsSZArray) - { - return ConvertVariantToSystemArrayOfType(p_var, type); - } - else if (type.IsGenericType) - { - if (typeof(Godot.Object).IsAssignableFrom(type)) - { - var godotObject = VariantUtils.ConvertToGodotObject(p_var); - - if (!type.IsInstanceOfType(godotObject)) - { - GD.PushError("Invalid cast when marshaling Godot.Object type." + - $" `{godotObject.GetType()}` is not assignable to `{type.FullName}`."); - return null; - } - - return godotObject; - } - - return null; - } - else if (type == typeof(Variant)) - { - return Variant.CreateCopyingBorrowed(p_var); - } - - if (ConvertVariantToManagedObjectOfClass(p_var, type, out object? res)) - return res; - - break; - } - } - - GD.PushError("Attempted to convert Variant to unsupported type. Name: " + - type.FullName + "."); - return null; - } - - private static object? ConvertVariantToSystemArrayOfType(in godot_variant p_var, Type type) - { - if (type == typeof(byte[])) - return VariantUtils.ConvertAsPackedByteArrayToSystemArray(p_var); - - if (type == typeof(int[])) - return VariantUtils.ConvertAsPackedInt32ArrayToSystemArray(p_var); - - if (type == typeof(long[])) - return VariantUtils.ConvertAsPackedInt64ArrayToSystemArray(p_var); - - if (type == typeof(float[])) - return VariantUtils.ConvertAsPackedFloat32ArrayToSystemArray(p_var); - - if (type == typeof(double[])) - return VariantUtils.ConvertAsPackedFloat64ArrayToSystemArray(p_var); - - if (type == typeof(string[])) - return VariantUtils.ConvertAsPackedStringArrayToSystemArray(p_var); - - if (type == typeof(Vector2[])) - return VariantUtils.ConvertAsPackedVector2ArrayToSystemArray(p_var); - - if (type == typeof(Vector3[])) - return VariantUtils.ConvertAsPackedVector3ArrayToSystemArray(p_var); - - if (type == typeof(Color[])) - return VariantUtils.ConvertAsPackedColorArrayToSystemArray(p_var); - - if (type == typeof(StringName[])) - return VariantUtils.ConvertToSystemArrayOfStringName(p_var); - - if (type == typeof(NodePath[])) - return VariantUtils.ConvertToSystemArrayOfNodePath(p_var); - - if (type == typeof(RID[])) - return VariantUtils.ConvertToSystemArrayOfRID(p_var); - - if (typeof(Godot.Object[]).IsAssignableFrom(type)) - return VariantUtils.ConvertToSystemArrayOfGodotObject(p_var, type); - - GD.PushError("Attempted to convert Variant to array of unsupported element type. Name: " + - type.GetElementType()!.FullName + "."); - return null; - } - - private static bool ConvertVariantToManagedObjectOfClass(in godot_variant p_var, Type type, - out object? res) - { - if (typeof(Godot.Object).IsAssignableFrom(type)) - { - if (p_var.Type == Variant.Type.Nil) - { - res = null; - return true; - } - - if (p_var.Type != Variant.Type.Object) - { - GD.PushError("Invalid cast when marshaling Godot.Object type." + - $" Variant type is `{p_var.Type}`; expected `{p_var.Object}`."); - res = null; - return true; - } - - var godotObjectPtr = VariantUtils.ConvertToGodotObjectPtr(p_var); - - if (godotObjectPtr == IntPtr.Zero) - { - res = null; - return true; - } - - var godotObject = InteropUtils.UnmanagedGetManaged(godotObjectPtr); - - if (!type.IsInstanceOfType(godotObject)) - { - GD.PushError("Invalid cast when marshaling Godot.Object type." + - $" `{godotObject.GetType()}` is not assignable to `{type.FullName}`."); - res = null; - return false; - } - - res = godotObject; - return true; - } - - if (typeof(StringName) == type) - { - res = VariantUtils.ConvertToStringNameObject(p_var); - return true; - } - - if (typeof(NodePath) == type) - { - res = VariantUtils.ConvertToNodePathObject(p_var); - return true; - } - - if (typeof(RID) == type) - { - res = VariantUtils.ConvertToRID(p_var); - return true; - } - - if (typeof(Collections.Dictionary) == type) - { - res = VariantUtils.ConvertToDictionaryObject(p_var); - return true; - } - - if (typeof(Collections.Array) == type) - { - res = VariantUtils.ConvertToArrayObject(p_var); - return true; - } - - res = null; - return false; - } - - public static unsafe object? ConvertVariantToManagedObject(in godot_variant p_var) - { - switch (p_var.Type) - { - case Variant.Type.Bool: - return p_var.Bool.ToBool(); - case Variant.Type.Int: - return p_var.Int; - case Variant.Type.Float: - { -#if REAL_T_IS_DOUBLE - return p_var.Float; -#else - return (float)p_var.Float; -#endif - } - case Variant.Type.String: - return ConvertStringToManaged(p_var.String); - case Variant.Type.Vector2: - return p_var.Vector2; - case Variant.Type.Vector2i: - return p_var.Vector2i; - case Variant.Type.Rect2: - return p_var.Rect2; - case Variant.Type.Rect2i: - return p_var.Rect2i; - case Variant.Type.Vector3: - return p_var.Vector3; - case Variant.Type.Vector3i: - return p_var.Vector3i; - case Variant.Type.Transform2d: - return *p_var.Transform2D; - case Variant.Type.Vector4: - return p_var.Vector4; - case Variant.Type.Vector4i: - return p_var.Vector4i; - case Variant.Type.Plane: - return p_var.Plane; - case Variant.Type.Quaternion: - return p_var.Quaternion; - case Variant.Type.Aabb: - return *p_var.AABB; - case Variant.Type.Basis: - return *p_var.Basis; - case Variant.Type.Transform3d: - return *p_var.Transform3D; - case Variant.Type.Projection: - return *p_var.Projection; - case Variant.Type.Color: - return p_var.Color; - case Variant.Type.StringName: - { - // The Variant owns the value, so we need to make a copy - return StringName.CreateTakingOwnershipOfDisposableValue( - NativeFuncs.godotsharp_string_name_new_copy(p_var.StringName)); - } - case Variant.Type.NodePath: - { - // The Variant owns the value, so we need to make a copy - return NodePath.CreateTakingOwnershipOfDisposableValue( - NativeFuncs.godotsharp_node_path_new_copy(p_var.NodePath)); - } - case Variant.Type.Rid: - return p_var.RID; - case Variant.Type.Object: - return InteropUtils.UnmanagedGetManaged(p_var.Object); - case Variant.Type.Callable: - return ConvertCallableToManaged(p_var.Callable); - case Variant.Type.Signal: - return ConvertSignalToManaged(p_var.Signal); - case Variant.Type.Dictionary: - { - // The Variant owns the value, so we need to make a copy - return Collections.Dictionary.CreateTakingOwnershipOfDisposableValue( - NativeFuncs.godotsharp_dictionary_new_copy(p_var.Dictionary)); - } - case Variant.Type.Array: - { - // The Variant owns the value, so we need to make a copy - return Collections.Array.CreateTakingOwnershipOfDisposableValue( - NativeFuncs.godotsharp_array_new_copy(p_var.Array)); - } - case Variant.Type.PackedByteArray: - return VariantUtils.ConvertAsPackedByteArrayToSystemArray(p_var); - case Variant.Type.PackedInt32Array: - return VariantUtils.ConvertAsPackedInt32ArrayToSystemArray(p_var); - case Variant.Type.PackedInt64Array: - return VariantUtils.ConvertAsPackedInt64ArrayToSystemArray(p_var); - case Variant.Type.PackedFloat32Array: - return VariantUtils.ConvertAsPackedFloat32ArrayToSystemArray(p_var); - case Variant.Type.PackedFloat64Array: - return VariantUtils.ConvertAsPackedFloat64ArrayToSystemArray(p_var); - case Variant.Type.PackedStringArray: - return VariantUtils.ConvertAsPackedStringArrayToSystemArray(p_var); - case Variant.Type.PackedVector2Array: - return VariantUtils.ConvertAsPackedVector2ArrayToSystemArray(p_var); - case Variant.Type.PackedVector3Array: - return VariantUtils.ConvertAsPackedVector3ArrayToSystemArray(p_var); - case Variant.Type.PackedColorArray: - return VariantUtils.ConvertAsPackedColorArrayToSystemArray(p_var); - default: - return null; - } - } - // String public static unsafe godot_string ConvertStringToNative(string? p_mono_string) @@ -824,22 +333,6 @@ namespace Godot.NativeInterop return ret; } - // TODO: This needs reflection. Look for an alternative. - internal static Godot.Object[] ConvertNativeGodotArrayToSystemArrayOfGodotObjectType(in godot_array p_array, - Type type) - { - var array = Collections.Array.CreateTakingOwnershipOfDisposableValue( - NativeFuncs.godotsharp_array_new_copy(p_array)); - - int length = array.Count; - var ret = (Godot.Object[])Activator.CreateInstance(type, length)!; - - for (int i = 0; i < length; i++) - ret[i] = array[i].AsGodotObject(); - - return ret; - } - internal static StringName[] ConvertNativeGodotArrayToSystemArrayOfStringName(in godot_array p_array) { var array = Collections.Array.CreateTakingOwnershipOfDisposableValue( diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index b30b6a0752..c7deb6423b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -414,21 +414,6 @@ namespace Godot.NativeInterop // StringExtensions - public static partial void godotsharp_string_md5_buffer(in godot_string p_self, - out godot_packed_byte_array r_md5_buffer); - - public static partial void godotsharp_string_md5_text(in godot_string p_self, out godot_string r_md5_text); - - public static partial int godotsharp_string_rfind(in godot_string p_self, in godot_string p_what, int p_from); - - public static partial int godotsharp_string_rfindn(in godot_string p_self, in godot_string p_what, int p_from); - - public static partial void godotsharp_string_sha256_buffer(in godot_string p_self, - out godot_packed_byte_array r_sha256_buffer); - - public static partial void godotsharp_string_sha256_text(in godot_string p_self, - out godot_string r_sha256_text); - public static partial void godotsharp_string_simplify_path(in godot_string p_self, out godot_string r_simplified_path); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs deleted file mode 100644 index 4b3db0c01a..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs +++ /dev/null @@ -1,1057 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; - -namespace Godot.NativeInterop; - -// TODO: Change VariantConversionCallbacks<T>. Store the callback in a static field for quick repeated access, instead of checking every time. -internal static unsafe class VariantConversionCallbacks -{ - internal static System.Collections.Generic.Dictionary<Type, (IntPtr ToVariant, IntPtr FromVariant)> - GenericConversionCallbacks = new(); - - [SuppressMessage("ReSharper", "RedundantNameQualifier")] - internal static delegate*<in T, godot_variant> GetToVariantCallback<T>() - { - static godot_variant FromBool(in bool @bool) => - VariantUtils.CreateFromBool(@bool); - - static godot_variant FromChar(in char @char) => - VariantUtils.CreateFromInt(@char); - - static godot_variant FromInt8(in sbyte @int8) => - VariantUtils.CreateFromInt(@int8); - - static godot_variant FromInt16(in short @int16) => - VariantUtils.CreateFromInt(@int16); - - static godot_variant FromInt32(in int @int32) => - VariantUtils.CreateFromInt(@int32); - - static godot_variant FromInt64(in long @int64) => - VariantUtils.CreateFromInt(@int64); - - static godot_variant FromUInt8(in byte @uint8) => - VariantUtils.CreateFromInt(@uint8); - - static godot_variant FromUInt16(in ushort @uint16) => - VariantUtils.CreateFromInt(@uint16); - - static godot_variant FromUInt32(in uint @uint32) => - VariantUtils.CreateFromInt(@uint32); - - static godot_variant FromUInt64(in ulong @uint64) => - VariantUtils.CreateFromInt(@uint64); - - static godot_variant FromFloat(in float @float) => - VariantUtils.CreateFromFloat(@float); - - static godot_variant FromDouble(in double @double) => - VariantUtils.CreateFromFloat(@double); - - static godot_variant FromVector2(in Vector2 @vector2) => - VariantUtils.CreateFromVector2(@vector2); - - static godot_variant FromVector2I(in Vector2i vector2I) => - VariantUtils.CreateFromVector2i(vector2I); - - static godot_variant FromRect2(in Rect2 @rect2) => - VariantUtils.CreateFromRect2(@rect2); - - static godot_variant FromRect2I(in Rect2i rect2I) => - VariantUtils.CreateFromRect2i(rect2I); - - static godot_variant FromTransform2D(in Transform2D @transform2D) => - VariantUtils.CreateFromTransform2D(@transform2D); - - static godot_variant FromVector3(in Vector3 @vector3) => - VariantUtils.CreateFromVector3(@vector3); - - static godot_variant FromVector3I(in Vector3i vector3I) => - VariantUtils.CreateFromVector3i(vector3I); - - static godot_variant FromBasis(in Basis @basis) => - VariantUtils.CreateFromBasis(@basis); - - static godot_variant FromQuaternion(in Quaternion @quaternion) => - VariantUtils.CreateFromQuaternion(@quaternion); - - static godot_variant FromTransform3D(in Transform3D @transform3d) => - VariantUtils.CreateFromTransform3D(@transform3d); - - static godot_variant FromVector4(in Vector4 @vector4) => - VariantUtils.CreateFromVector4(@vector4); - - static godot_variant FromVector4I(in Vector4i vector4I) => - VariantUtils.CreateFromVector4i(vector4I); - - static godot_variant FromAabb(in AABB @aabb) => - VariantUtils.CreateFromAABB(@aabb); - - static godot_variant FromColor(in Color @color) => - VariantUtils.CreateFromColor(@color); - - static godot_variant FromPlane(in Plane @plane) => - VariantUtils.CreateFromPlane(@plane); - - static godot_variant FromCallable(in Callable @callable) => - VariantUtils.CreateFromCallable(@callable); - - static godot_variant FromSignalInfo(in SignalInfo @signalInfo) => - VariantUtils.CreateFromSignalInfo(@signalInfo); - - static godot_variant FromString(in string @string) => - VariantUtils.CreateFromString(@string); - - static godot_variant FromByteArray(in byte[] byteArray) => - VariantUtils.CreateFromPackedByteArray(byteArray); - - static godot_variant FromInt32Array(in int[] int32Array) => - VariantUtils.CreateFromPackedInt32Array(int32Array); - - static godot_variant FromInt64Array(in long[] int64Array) => - VariantUtils.CreateFromPackedInt64Array(int64Array); - - static godot_variant FromFloatArray(in float[] floatArray) => - VariantUtils.CreateFromPackedFloat32Array(floatArray); - - static godot_variant FromDoubleArray(in double[] doubleArray) => - VariantUtils.CreateFromPackedFloat64Array(doubleArray); - - static godot_variant FromStringArray(in string[] stringArray) => - VariantUtils.CreateFromPackedStringArray(stringArray); - - static godot_variant FromVector2Array(in Vector2[] vector2Array) => - VariantUtils.CreateFromPackedVector2Array(vector2Array); - - static godot_variant FromVector3Array(in Vector3[] vector3Array) => - VariantUtils.CreateFromPackedVector3Array(vector3Array); - - static godot_variant FromColorArray(in Color[] colorArray) => - VariantUtils.CreateFromPackedColorArray(colorArray); - - static godot_variant FromStringNameArray(in StringName[] stringNameArray) => - VariantUtils.CreateFromSystemArrayOfStringName(stringNameArray); - - static godot_variant FromNodePathArray(in NodePath[] nodePathArray) => - VariantUtils.CreateFromSystemArrayOfNodePath(nodePathArray); - - static godot_variant FromRidArray(in RID[] ridArray) => - VariantUtils.CreateFromSystemArrayOfRID(ridArray); - - static godot_variant FromGodotObject(in Godot.Object godotObject) => - VariantUtils.CreateFromGodotObject(godotObject); - - static godot_variant FromStringName(in StringName stringName) => - VariantUtils.CreateFromStringName(stringName); - - static godot_variant FromNodePath(in NodePath nodePath) => - VariantUtils.CreateFromNodePath(nodePath); - - static godot_variant FromRid(in RID rid) => - VariantUtils.CreateFromRID(rid); - - static godot_variant FromGodotDictionary(in Collections.Dictionary godotDictionary) => - VariantUtils.CreateFromDictionary(godotDictionary); - - static godot_variant FromGodotArray(in Collections.Array godotArray) => - VariantUtils.CreateFromArray(godotArray); - - static godot_variant FromVariant(in Variant variant) => - NativeFuncs.godotsharp_variant_new_copy((godot_variant)variant.NativeVar); - - var typeOfT = typeof(T); - - if (typeOfT == typeof(bool)) - { - return (delegate*<in T, godot_variant>)(delegate*<in bool, godot_variant>) - &FromBool; - } - - if (typeOfT == typeof(char)) - { - return (delegate*<in T, godot_variant>)(delegate*<in char, godot_variant>) - &FromChar; - } - - if (typeOfT == typeof(sbyte)) - { - return (delegate*<in T, godot_variant>)(delegate*<in sbyte, godot_variant>) - &FromInt8; - } - - if (typeOfT == typeof(short)) - { - return (delegate*<in T, godot_variant>)(delegate*<in short, godot_variant>) - &FromInt16; - } - - if (typeOfT == typeof(int)) - { - return (delegate*<in T, godot_variant>)(delegate*<in int, godot_variant>) - &FromInt32; - } - - if (typeOfT == typeof(long)) - { - return (delegate*<in T, godot_variant>)(delegate*<in long, godot_variant>) - &FromInt64; - } - - if (typeOfT == typeof(byte)) - { - return (delegate*<in T, godot_variant>)(delegate*<in byte, godot_variant>) - &FromUInt8; - } - - if (typeOfT == typeof(ushort)) - { - return (delegate*<in T, godot_variant>)(delegate*<in ushort, godot_variant>) - &FromUInt16; - } - - if (typeOfT == typeof(uint)) - { - return (delegate*<in T, godot_variant>)(delegate*<in uint, godot_variant>) - &FromUInt32; - } - - if (typeOfT == typeof(ulong)) - { - return (delegate*<in T, godot_variant>)(delegate*<in ulong, godot_variant>) - &FromUInt64; - } - - if (typeOfT == typeof(float)) - { - return (delegate*<in T, godot_variant>)(delegate*<in float, godot_variant>) - &FromFloat; - } - - if (typeOfT == typeof(double)) - { - return (delegate*<in T, godot_variant>)(delegate*<in double, godot_variant>) - &FromDouble; - } - - if (typeOfT == typeof(Vector2)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector2, godot_variant>) - &FromVector2; - } - - if (typeOfT == typeof(Vector2i)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector2i, godot_variant>) - &FromVector2I; - } - - if (typeOfT == typeof(Rect2)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Rect2, godot_variant>) - &FromRect2; - } - - if (typeOfT == typeof(Rect2i)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Rect2i, godot_variant>) - &FromRect2I; - } - - if (typeOfT == typeof(Transform2D)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Transform2D, godot_variant>) - &FromTransform2D; - } - - if (typeOfT == typeof(Vector3)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector3, godot_variant>) - &FromVector3; - } - - if (typeOfT == typeof(Vector3i)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector3i, godot_variant>) - &FromVector3I; - } - - if (typeOfT == typeof(Basis)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Basis, godot_variant>) - &FromBasis; - } - - if (typeOfT == typeof(Quaternion)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Quaternion, godot_variant>) - &FromQuaternion; - } - - if (typeOfT == typeof(Transform3D)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Transform3D, godot_variant>) - &FromTransform3D; - } - - if (typeOfT == typeof(Vector4)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector4, godot_variant>) - &FromVector4; - } - - if (typeOfT == typeof(Vector4i)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector4i, godot_variant>) - &FromVector4I; - } - - if (typeOfT == typeof(AABB)) - { - return (delegate*<in T, godot_variant>)(delegate*<in AABB, godot_variant>) - &FromAabb; - } - - if (typeOfT == typeof(Color)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Color, godot_variant>) - &FromColor; - } - - if (typeOfT == typeof(Plane)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Plane, godot_variant>) - &FromPlane; - } - - if (typeOfT == typeof(Callable)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Callable, godot_variant>) - &FromCallable; - } - - if (typeOfT == typeof(SignalInfo)) - { - return (delegate*<in T, godot_variant>)(delegate*<in SignalInfo, godot_variant>) - &FromSignalInfo; - } - - if (typeOfT.IsEnum) - { - var enumUnderlyingType = typeOfT.GetEnumUnderlyingType(); - - switch (Type.GetTypeCode(enumUnderlyingType)) - { - case TypeCode.SByte: - { - return (delegate*<in T, godot_variant>)(delegate*<in sbyte, godot_variant>) - &FromInt8; - } - case TypeCode.Int16: - { - return (delegate*<in T, godot_variant>)(delegate*<in short, godot_variant>) - &FromInt16; - } - case TypeCode.Int32: - { - return (delegate*<in T, godot_variant>)(delegate*<in int, godot_variant>) - &FromInt32; - } - case TypeCode.Int64: - { - return (delegate*<in T, godot_variant>)(delegate*<in long, godot_variant>) - &FromInt64; - } - case TypeCode.Byte: - { - return (delegate*<in T, godot_variant>)(delegate*<in byte, godot_variant>) - &FromUInt8; - } - case TypeCode.UInt16: - { - return (delegate*<in T, godot_variant>)(delegate*<in ushort, godot_variant>) - &FromUInt16; - } - case TypeCode.UInt32: - { - return (delegate*<in T, godot_variant>)(delegate*<in uint, godot_variant>) - &FromUInt32; - } - case TypeCode.UInt64: - { - return (delegate*<in T, godot_variant>)(delegate*<in ulong, godot_variant>) - &FromUInt64; - } - default: - return null; - } - } - - if (typeOfT == typeof(string)) - { - return (delegate*<in T, godot_variant>)(delegate*<in string, godot_variant>) - &FromString; - } - - if (typeOfT == typeof(byte[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in byte[], godot_variant>) - &FromByteArray; - } - - if (typeOfT == typeof(int[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in int[], godot_variant>) - &FromInt32Array; - } - - if (typeOfT == typeof(long[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in long[], godot_variant>) - &FromInt64Array; - } - - if (typeOfT == typeof(float[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in float[], godot_variant>) - &FromFloatArray; - } - - if (typeOfT == typeof(double[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in double[], godot_variant>) - &FromDoubleArray; - } - - if (typeOfT == typeof(string[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in string[], godot_variant>) - &FromStringArray; - } - - if (typeOfT == typeof(Vector2[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector2[], godot_variant>) - &FromVector2Array; - } - - if (typeOfT == typeof(Vector3[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in Vector3[], godot_variant>) - &FromVector3Array; - } - - if (typeOfT == typeof(Color[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in Color[], godot_variant>) - &FromColorArray; - } - - if (typeOfT == typeof(StringName[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in StringName[], godot_variant>) - &FromStringNameArray; - } - - if (typeOfT == typeof(NodePath[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in NodePath[], godot_variant>) - &FromNodePathArray; - } - - if (typeOfT == typeof(RID[])) - { - return (delegate*<in T, godot_variant>)(delegate*<in RID[], godot_variant>) - &FromRidArray; - } - - if (typeof(Godot.Object).IsAssignableFrom(typeOfT)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Godot.Object, godot_variant>) - &FromGodotObject; - } - - if (typeOfT == typeof(StringName)) - { - return (delegate*<in T, godot_variant>)(delegate*<in StringName, godot_variant>) - &FromStringName; - } - - if (typeOfT == typeof(NodePath)) - { - return (delegate*<in T, godot_variant>)(delegate*<in NodePath, godot_variant>) - &FromNodePath; - } - - if (typeOfT == typeof(RID)) - { - return (delegate*<in T, godot_variant>)(delegate*<in RID, godot_variant>) - &FromRid; - } - - if (typeOfT == typeof(Godot.Collections.Dictionary)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Godot.Collections.Dictionary, godot_variant>) - &FromGodotDictionary; - } - - if (typeOfT == typeof(Godot.Collections.Array)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Godot.Collections.Array, godot_variant>) - &FromGodotArray; - } - - if (typeOfT == typeof(Variant)) - { - return (delegate*<in T, godot_variant>)(delegate*<in Variant, godot_variant>) - &FromVariant; - } - - // TODO: - // IsGenericType and GetGenericTypeDefinition don't work in NativeAOT's reflection-free mode. - // We could make the Godot collections implement an interface and use IsAssignableFrom instead. - // Or we could just skip the check and always look for a conversion callback for the type. - if (typeOfT.IsGenericType) - { - var genericTypeDef = typeOfT.GetGenericTypeDefinition(); - - if (genericTypeDef == typeof(Godot.Collections.Dictionary<,>) || - genericTypeDef == typeof(Godot.Collections.Array<>)) - { - RuntimeHelpers.RunClassConstructor(typeOfT.TypeHandle); - - if (GenericConversionCallbacks.TryGetValue(typeOfT, out var genericConversion)) - { - return (delegate*<in T, godot_variant>)genericConversion.ToVariant; - } - } - } - - return null; - } - - [SuppressMessage("ReSharper", "RedundantNameQualifier")] - internal static delegate*<in godot_variant, T> GetToManagedCallback<T>() - { - static bool ToBool(in godot_variant variant) => - VariantUtils.ConvertToBool(variant); - - static char ToChar(in godot_variant variant) => - VariantUtils.ConvertToChar(variant); - - static sbyte ToInt8(in godot_variant variant) => - VariantUtils.ConvertToInt8(variant); - - static short ToInt16(in godot_variant variant) => - VariantUtils.ConvertToInt16(variant); - - static int ToInt32(in godot_variant variant) => - VariantUtils.ConvertToInt32(variant); - - static long ToInt64(in godot_variant variant) => - VariantUtils.ConvertToInt64(variant); - - static byte ToUInt8(in godot_variant variant) => - VariantUtils.ConvertToUInt8(variant); - - static ushort ToUInt16(in godot_variant variant) => - VariantUtils.ConvertToUInt16(variant); - - static uint ToUInt32(in godot_variant variant) => - VariantUtils.ConvertToUInt32(variant); - - static ulong ToUInt64(in godot_variant variant) => - VariantUtils.ConvertToUInt64(variant); - - static float ToFloat(in godot_variant variant) => - VariantUtils.ConvertToFloat32(variant); - - static double ToDouble(in godot_variant variant) => - VariantUtils.ConvertToFloat64(variant); - - static Vector2 ToVector2(in godot_variant variant) => - VariantUtils.ConvertToVector2(variant); - - static Vector2i ToVector2I(in godot_variant variant) => - VariantUtils.ConvertToVector2i(variant); - - static Rect2 ToRect2(in godot_variant variant) => - VariantUtils.ConvertToRect2(variant); - - static Rect2i ToRect2I(in godot_variant variant) => - VariantUtils.ConvertToRect2i(variant); - - static Transform2D ToTransform2D(in godot_variant variant) => - VariantUtils.ConvertToTransform2D(variant); - - static Vector3 ToVector3(in godot_variant variant) => - VariantUtils.ConvertToVector3(variant); - - static Vector3i ToVector3I(in godot_variant variant) => - VariantUtils.ConvertToVector3i(variant); - - static Basis ToBasis(in godot_variant variant) => - VariantUtils.ConvertToBasis(variant); - - static Quaternion ToQuaternion(in godot_variant variant) => - VariantUtils.ConvertToQuaternion(variant); - - static Transform3D ToTransform3D(in godot_variant variant) => - VariantUtils.ConvertToTransform3D(variant); - - static Vector4 ToVector4(in godot_variant variant) => - VariantUtils.ConvertToVector4(variant); - - static Vector4i ToVector4I(in godot_variant variant) => - VariantUtils.ConvertToVector4i(variant); - - static AABB ToAabb(in godot_variant variant) => - VariantUtils.ConvertToAABB(variant); - - static Color ToColor(in godot_variant variant) => - VariantUtils.ConvertToColor(variant); - - static Plane ToPlane(in godot_variant variant) => - VariantUtils.ConvertToPlane(variant); - - static Callable ToCallable(in godot_variant variant) => - VariantUtils.ConvertToCallableManaged(variant); - - static SignalInfo ToSignalInfo(in godot_variant variant) => - VariantUtils.ConvertToSignalInfo(variant); - - static string ToString(in godot_variant variant) => - VariantUtils.ConvertToStringObject(variant); - - static byte[] ToByteArray(in godot_variant variant) => - VariantUtils.ConvertAsPackedByteArrayToSystemArray(variant); - - static int[] ToInt32Array(in godot_variant variant) => - VariantUtils.ConvertAsPackedInt32ArrayToSystemArray(variant); - - static long[] ToInt64Array(in godot_variant variant) => - VariantUtils.ConvertAsPackedInt64ArrayToSystemArray(variant); - - static float[] ToFloatArray(in godot_variant variant) => - VariantUtils.ConvertAsPackedFloat32ArrayToSystemArray(variant); - - static double[] ToDoubleArray(in godot_variant variant) => - VariantUtils.ConvertAsPackedFloat64ArrayToSystemArray(variant); - - static string[] ToStringArray(in godot_variant variant) => - VariantUtils.ConvertAsPackedStringArrayToSystemArray(variant); - - static Vector2[] ToVector2Array(in godot_variant variant) => - VariantUtils.ConvertAsPackedVector2ArrayToSystemArray(variant); - - static Vector3[] ToVector3Array(in godot_variant variant) => - VariantUtils.ConvertAsPackedVector3ArrayToSystemArray(variant); - - static Color[] ToColorArray(in godot_variant variant) => - VariantUtils.ConvertAsPackedColorArrayToSystemArray(variant); - - static StringName[] ToStringNameArray(in godot_variant variant) => - VariantUtils.ConvertToSystemArrayOfStringName(variant); - - static NodePath[] ToNodePathArray(in godot_variant variant) => - VariantUtils.ConvertToSystemArrayOfNodePath(variant); - - static RID[] ToRidArray(in godot_variant variant) => - VariantUtils.ConvertToSystemArrayOfRID(variant); - - static Godot.Object ToGodotObject(in godot_variant variant) => - VariantUtils.ConvertToGodotObject(variant); - - static StringName ToStringName(in godot_variant variant) => - VariantUtils.ConvertToStringNameObject(variant); - - static NodePath ToNodePath(in godot_variant variant) => - VariantUtils.ConvertToNodePathObject(variant); - - static RID ToRid(in godot_variant variant) => - VariantUtils.ConvertToRID(variant); - - static Collections.Dictionary ToGodotDictionary(in godot_variant variant) => - VariantUtils.ConvertToDictionaryObject(variant); - - static Collections.Array ToGodotArray(in godot_variant variant) => - VariantUtils.ConvertToArrayObject(variant); - - static Variant ToVariant(in godot_variant variant) => - Variant.CreateCopyingBorrowed(variant); - - var typeOfT = typeof(T); - - // ReSharper disable RedundantCast - // Rider is being stupid here. These casts are definitely needed. We get build errors without them. - - if (typeOfT == typeof(bool)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, bool>) - &ToBool; - } - - if (typeOfT == typeof(char)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, char>) - &ToChar; - } - - if (typeOfT == typeof(sbyte)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, sbyte>) - &ToInt8; - } - - if (typeOfT == typeof(short)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, short>) - &ToInt16; - } - - if (typeOfT == typeof(int)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, int>) - &ToInt32; - } - - if (typeOfT == typeof(long)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, long>) - &ToInt64; - } - - if (typeOfT == typeof(byte)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, byte>) - &ToUInt8; - } - - if (typeOfT == typeof(ushort)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, ushort>) - &ToUInt16; - } - - if (typeOfT == typeof(uint)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, uint>) - &ToUInt32; - } - - if (typeOfT == typeof(ulong)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, ulong>) - &ToUInt64; - } - - if (typeOfT == typeof(float)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, float>) - &ToFloat; - } - - if (typeOfT == typeof(double)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, double>) - &ToDouble; - } - - if (typeOfT == typeof(Vector2)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector2>) - &ToVector2; - } - - if (typeOfT == typeof(Vector2i)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector2i>) - &ToVector2I; - } - - if (typeOfT == typeof(Rect2)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Rect2>) - &ToRect2; - } - - if (typeOfT == typeof(Rect2i)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Rect2i>) - &ToRect2I; - } - - if (typeOfT == typeof(Transform2D)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Transform2D>) - &ToTransform2D; - } - - if (typeOfT == typeof(Vector3)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector3>) - &ToVector3; - } - - if (typeOfT == typeof(Vector3i)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector3i>) - &ToVector3I; - } - - if (typeOfT == typeof(Basis)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Basis>) - &ToBasis; - } - - if (typeOfT == typeof(Quaternion)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Quaternion>) - &ToQuaternion; - } - - if (typeOfT == typeof(Transform3D)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Transform3D>) - &ToTransform3D; - } - - if (typeOfT == typeof(Vector4)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector4>) - &ToVector4; - } - - if (typeOfT == typeof(Vector4i)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector4i>) - &ToVector4I; - } - - if (typeOfT == typeof(AABB)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, AABB>) - &ToAabb; - } - - if (typeOfT == typeof(Color)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Color>) - &ToColor; - } - - if (typeOfT == typeof(Plane)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Plane>) - &ToPlane; - } - - if (typeOfT == typeof(Callable)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Callable>) - &ToCallable; - } - - if (typeOfT == typeof(SignalInfo)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, SignalInfo>) - &ToSignalInfo; - } - - if (typeOfT.IsEnum) - { - var enumUnderlyingType = typeOfT.GetEnumUnderlyingType(); - - switch (Type.GetTypeCode(enumUnderlyingType)) - { - case TypeCode.SByte: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, sbyte>) - &ToInt8; - } - case TypeCode.Int16: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, short>) - &ToInt16; - } - case TypeCode.Int32: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, int>) - &ToInt32; - } - case TypeCode.Int64: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, long>) - &ToInt64; - } - case TypeCode.Byte: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, byte>) - &ToUInt8; - } - case TypeCode.UInt16: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, ushort>) - &ToUInt16; - } - case TypeCode.UInt32: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, uint>) - &ToUInt32; - } - case TypeCode.UInt64: - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, ulong>) - &ToUInt64; - } - default: - return null; - } - } - - if (typeOfT == typeof(string)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, string>) - &ToString; - } - - if (typeOfT == typeof(byte[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, byte[]>) - &ToByteArray; - } - - if (typeOfT == typeof(int[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, int[]>) - &ToInt32Array; - } - - if (typeOfT == typeof(long[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, long[]>) - &ToInt64Array; - } - - if (typeOfT == typeof(float[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, float[]>) - &ToFloatArray; - } - - if (typeOfT == typeof(double[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, double[]>) - &ToDoubleArray; - } - - if (typeOfT == typeof(string[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, string[]>) - &ToStringArray; - } - - if (typeOfT == typeof(Vector2[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector2[]>) - &ToVector2Array; - } - - if (typeOfT == typeof(Vector3[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Vector3[]>) - &ToVector3Array; - } - - if (typeOfT == typeof(Color[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Color[]>) - &ToColorArray; - } - - if (typeOfT == typeof(StringName[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, StringName[]>) - &ToStringNameArray; - } - - if (typeOfT == typeof(NodePath[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, NodePath[]>) - &ToNodePathArray; - } - - if (typeOfT == typeof(RID[])) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, RID[]>) - &ToRidArray; - } - - if (typeof(Godot.Object).IsAssignableFrom(typeOfT)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Godot.Object>) - &ToGodotObject; - } - - if (typeOfT == typeof(StringName)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, StringName>) - &ToStringName; - } - - if (typeOfT == typeof(NodePath)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, NodePath>) - &ToNodePath; - } - - if (typeOfT == typeof(RID)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, RID>) - &ToRid; - } - - if (typeOfT == typeof(Godot.Collections.Dictionary)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Godot.Collections.Dictionary>) - &ToGodotDictionary; - } - - if (typeOfT == typeof(Godot.Collections.Array)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Godot.Collections.Array>) - &ToGodotArray; - } - - if (typeOfT == typeof(Variant)) - { - return (delegate*<in godot_variant, T>)(delegate*<in godot_variant, Variant>) - &ToVariant; - } - - // TODO: - // IsGenericType and GetGenericTypeDefinition don't work in NativeAOT's reflection-free mode. - // We could make the Godot collections implement an interface and use IsAssignableFrom instead. - // Or we could just skip the check and always look for a conversion callback for the type. - if (typeOfT.IsGenericType) - { - var genericTypeDef = typeOfT.GetGenericTypeDefinition(); - - if (genericTypeDef == typeof(Godot.Collections.Dictionary<,>) || - genericTypeDef == typeof(Godot.Collections.Array<>)) - { - RuntimeHelpers.RunClassConstructor(typeOfT.TypeHandle); - - if (GenericConversionCallbacks.TryGetValue(typeOfT, out var genericConversion)) - { - return (delegate*<in godot_variant, T>)genericConversion.FromVariant; - } - } - } - - // ReSharper restore RedundantCast - - return null; - } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantSpanHelpers.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantSpanHelpers.cs deleted file mode 100644 index 46f31bbf4e..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantSpanHelpers.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; - -namespace Godot.NativeInterop -{ - internal readonly ref struct VariantSpanDisposer - { - private readonly Span<godot_variant.movable> _variantSpan; - - // IMPORTANT: The span element must be default initialized. - // Make sure call Clear() on the span if it was created with stackalloc. - public VariantSpanDisposer(Span<godot_variant.movable> variantSpan) - { - _variantSpan = variantSpan; - } - - public void Dispose() - { - for (int i = 0; i < _variantSpan.Length; i++) - _variantSpan[i].DangerousSelfRef.Dispose(); - } - } - - internal static class VariantSpanExtensions - { - // Used to make sure we always initialize the span values to the default, - // as we need that in order to safely dispose all elements after. - public static Span<godot_variant.movable> Cleared(this Span<godot_variant.movable> span) - { - span.Clear(); - return span; - } - } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs index 57f9ec7d95..11f1e31384 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs @@ -8,7 +8,7 @@ using Godot.Collections; namespace Godot.NativeInterop { - public static class VariantUtils + public static partial class VariantUtils { public static godot_variant CreateFromRID(RID from) => new() { Type = Variant.Type.Rid, RID = from }; @@ -594,12 +594,5 @@ namespace Godot.NativeInterop using var godotArray = NativeFuncs.godotsharp_variant_as_array(p_var); return Marshaling.ConvertNativeGodotArrayToSystemArrayOfGodotObjectType<T>(godotArray); } - - // ReSharper disable once RedundantNameQualifier - public static Godot.Object[] ConvertToSystemArrayOfGodotObject(in godot_variant p_var, Type type) - { - using var godotArray = NativeFuncs.godotsharp_variant_as_array(p_var); - return Marshaling.ConvertNativeGodotArrayToSystemArrayOfGodotObjectType(godotArray, type); - } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.generic.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.generic.cs new file mode 100644 index 0000000000..80ef2a1ea1 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.generic.cs @@ -0,0 +1,408 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace Godot.NativeInterop; + +#nullable enable + +public partial class VariantUtils +{ + private static Exception UnsupportedType<T>() => new InvalidOperationException( + $"The type is not supported for conversion to/from Variant: '{typeof(T).FullName}'"); + + internal static class GenericConversion<T> + { + public static unsafe godot_variant ToVariant(in T from) => + ToVariantCb != null ? ToVariantCb(from) : throw UnsupportedType<T>(); + + public static unsafe T FromVariant(in godot_variant variant) => + FromVariantCb != null ? FromVariantCb(variant) : throw UnsupportedType<T>(); + + // ReSharper disable once StaticMemberInGenericType + internal static unsafe delegate*<in T, godot_variant> ToVariantCb; + + // ReSharper disable once StaticMemberInGenericType + internal static unsafe delegate*<in godot_variant, T> FromVariantCb; + + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + static GenericConversion() + { + RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + public static godot_variant CreateFrom<[MustBeVariant] T>(in T from) + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static TTo UnsafeAs<TTo>(in T f) => Unsafe.As<T, TTo>(ref Unsafe.AsRef(f)); + + // `typeof(T) == typeof(X)` is optimized away. We cannot cache `typeof(T)` in a local variable, as it's not optimized when done like that. + + if (typeof(T) == typeof(bool)) + return CreateFromBool(UnsafeAs<bool>(from)); + + if (typeof(T) == typeof(char)) + return CreateFromInt(UnsafeAs<char>(from)); + + if (typeof(T) == typeof(sbyte)) + return CreateFromInt(UnsafeAs<sbyte>(from)); + + if (typeof(T) == typeof(short)) + return CreateFromInt(UnsafeAs<short>(from)); + + if (typeof(T) == typeof(int)) + return CreateFromInt(UnsafeAs<int>(from)); + + if (typeof(T) == typeof(long)) + return CreateFromInt(UnsafeAs<long>(from)); + + if (typeof(T) == typeof(byte)) + return CreateFromInt(UnsafeAs<byte>(from)); + + if (typeof(T) == typeof(ushort)) + return CreateFromInt(UnsafeAs<ushort>(from)); + + if (typeof(T) == typeof(uint)) + return CreateFromInt(UnsafeAs<uint>(from)); + + if (typeof(T) == typeof(ulong)) + return CreateFromInt(UnsafeAs<ulong>(from)); + + if (typeof(T) == typeof(float)) + return CreateFromFloat(UnsafeAs<float>(from)); + + if (typeof(T) == typeof(double)) + return CreateFromFloat(UnsafeAs<double>(from)); + + if (typeof(T) == typeof(Vector2)) + return CreateFromVector2(UnsafeAs<Vector2>(from)); + + if (typeof(T) == typeof(Vector2i)) + return CreateFromVector2i(UnsafeAs<Vector2i>(from)); + + if (typeof(T) == typeof(Rect2)) + return CreateFromRect2(UnsafeAs<Rect2>(from)); + + if (typeof(T) == typeof(Rect2i)) + return CreateFromRect2i(UnsafeAs<Rect2i>(from)); + + if (typeof(T) == typeof(Transform2D)) + return CreateFromTransform2D(UnsafeAs<Transform2D>(from)); + + if (typeof(T) == typeof(Vector3)) + return CreateFromVector3(UnsafeAs<Vector3>(from)); + + if (typeof(T) == typeof(Vector3i)) + return CreateFromVector3i(UnsafeAs<Vector3i>(from)); + + if (typeof(T) == typeof(Basis)) + return CreateFromBasis(UnsafeAs<Basis>(from)); + + if (typeof(T) == typeof(Quaternion)) + return CreateFromQuaternion(UnsafeAs<Quaternion>(from)); + + if (typeof(T) == typeof(Transform3D)) + return CreateFromTransform3D(UnsafeAs<Transform3D>(from)); + + if (typeof(T) == typeof(Vector4)) + return CreateFromVector4(UnsafeAs<Vector4>(from)); + + if (typeof(T) == typeof(Vector4i)) + return CreateFromVector4i(UnsafeAs<Vector4i>(from)); + + if (typeof(T) == typeof(AABB)) + return CreateFromAABB(UnsafeAs<AABB>(from)); + + if (typeof(T) == typeof(Color)) + return CreateFromColor(UnsafeAs<Color>(from)); + + if (typeof(T) == typeof(Plane)) + return CreateFromPlane(UnsafeAs<Plane>(from)); + + if (typeof(T) == typeof(Callable)) + return CreateFromCallable(UnsafeAs<Callable>(from)); + + if (typeof(T) == typeof(SignalInfo)) + return CreateFromSignalInfo(UnsafeAs<SignalInfo>(from)); + + if (typeof(T) == typeof(string)) + return CreateFromString(UnsafeAs<string>(from)); + + if (typeof(T) == typeof(byte[])) + return CreateFromPackedByteArray(UnsafeAs<byte[]>(from)); + + if (typeof(T) == typeof(int[])) + return CreateFromPackedInt32Array(UnsafeAs<int[]>(from)); + + if (typeof(T) == typeof(long[])) + return CreateFromPackedInt64Array(UnsafeAs<long[]>(from)); + + if (typeof(T) == typeof(float[])) + return CreateFromPackedFloat32Array(UnsafeAs<float[]>(from)); + + if (typeof(T) == typeof(double[])) + return CreateFromPackedFloat64Array(UnsafeAs<double[]>(from)); + + if (typeof(T) == typeof(string[])) + return CreateFromPackedStringArray(UnsafeAs<string[]>(from)); + + if (typeof(T) == typeof(Vector2[])) + return CreateFromPackedVector2Array(UnsafeAs<Vector2[]>(from)); + + if (typeof(T) == typeof(Vector3[])) + return CreateFromPackedVector3Array(UnsafeAs<Vector3[]>(from)); + + if (typeof(T) == typeof(Color[])) + return CreateFromPackedColorArray(UnsafeAs<Color[]>(from)); + + if (typeof(T) == typeof(StringName[])) + return CreateFromSystemArrayOfStringName(UnsafeAs<StringName[]>(from)); + + if (typeof(T) == typeof(NodePath[])) + return CreateFromSystemArrayOfNodePath(UnsafeAs<NodePath[]>(from)); + + if (typeof(T) == typeof(RID[])) + return CreateFromSystemArrayOfRID(UnsafeAs<RID[]>(from)); + + if (typeof(T) == typeof(StringName)) + return CreateFromStringName(UnsafeAs<StringName>(from)); + + if (typeof(T) == typeof(NodePath)) + return CreateFromNodePath(UnsafeAs<NodePath>(from)); + + if (typeof(T) == typeof(RID)) + return CreateFromRID(UnsafeAs<RID>(from)); + + if (typeof(T) == typeof(Godot.Collections.Dictionary)) + return CreateFromDictionary(UnsafeAs<Godot.Collections.Dictionary>(from)); + + if (typeof(T) == typeof(Godot.Collections.Array)) + return CreateFromArray(UnsafeAs<Godot.Collections.Array>(from)); + + if (typeof(T) == typeof(Variant)) + return NativeFuncs.godotsharp_variant_new_copy((godot_variant)UnsafeAs<Variant>(from).NativeVar); + + // More complex checks here at the end, to avoid screwing the simple ones in case they're not optimized away. + + // `typeof(X).IsAssignableFrom(typeof(T))` is optimized away + + if (typeof(Godot.Object).IsAssignableFrom(typeof(T))) + return CreateFromGodotObject(UnsafeAs<Godot.Object>(from)); + + // `typeof(T).IsValueType` is optimized away + // `typeof(T).IsEnum` is NOT optimized away: https://github.com/dotnet/runtime/issues/67113 + // Fortunately, `typeof(System.Enum).IsAssignableFrom(typeof(T))` does the job! + + if (typeof(T).IsValueType && typeof(System.Enum).IsAssignableFrom(typeof(T))) + { + // `Type.GetTypeCode(typeof(T).GetEnumUnderlyingType())` is not optimized away. + // Fortunately, `Unsafe.SizeOf<T>()` works and is optimized away. + // We don't need to know whether it's signed or unsigned. + + if (Unsafe.SizeOf<T>() == 1) + return CreateFromInt(UnsafeAs<sbyte>(from)); + + if (Unsafe.SizeOf<T>() == 2) + return CreateFromInt(UnsafeAs<short>(from)); + + if (Unsafe.SizeOf<T>() == 4) + return CreateFromInt(UnsafeAs<int>(from)); + + if (Unsafe.SizeOf<T>() == 8) + return CreateFromInt(UnsafeAs<long>(from)); + + throw UnsupportedType<T>(); + } + + return GenericConversion<T>.ToVariant(from); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + public static T ConvertTo<[MustBeVariant] T>(in godot_variant variant) + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static T UnsafeAsT<TFrom>(TFrom f) => Unsafe.As<TFrom, T>(ref Unsafe.AsRef(f)); + + if (typeof(T) == typeof(bool)) + return UnsafeAsT(ConvertToBool(variant)); + + if (typeof(T) == typeof(char)) + return UnsafeAsT(ConvertToChar(variant)); + + if (typeof(T) == typeof(sbyte)) + return UnsafeAsT(ConvertToInt8(variant)); + + if (typeof(T) == typeof(short)) + return UnsafeAsT(ConvertToInt16(variant)); + + if (typeof(T) == typeof(int)) + return UnsafeAsT(ConvertToInt32(variant)); + + if (typeof(T) == typeof(long)) + return UnsafeAsT(ConvertToInt64(variant)); + + if (typeof(T) == typeof(byte)) + return UnsafeAsT(ConvertToUInt8(variant)); + + if (typeof(T) == typeof(ushort)) + return UnsafeAsT(ConvertToUInt16(variant)); + + if (typeof(T) == typeof(uint)) + return UnsafeAsT(ConvertToUInt32(variant)); + + if (typeof(T) == typeof(ulong)) + return UnsafeAsT(ConvertToUInt64(variant)); + + if (typeof(T) == typeof(float)) + return UnsafeAsT(ConvertToFloat32(variant)); + + if (typeof(T) == typeof(double)) + return UnsafeAsT(ConvertToFloat64(variant)); + + if (typeof(T) == typeof(Vector2)) + return UnsafeAsT(ConvertToVector2(variant)); + + if (typeof(T) == typeof(Vector2i)) + return UnsafeAsT(ConvertToVector2i(variant)); + + if (typeof(T) == typeof(Rect2)) + return UnsafeAsT(ConvertToRect2(variant)); + + if (typeof(T) == typeof(Rect2i)) + return UnsafeAsT(ConvertToRect2i(variant)); + + if (typeof(T) == typeof(Transform2D)) + return UnsafeAsT(ConvertToTransform2D(variant)); + + if (typeof(T) == typeof(Vector3)) + return UnsafeAsT(ConvertToVector3(variant)); + + if (typeof(T) == typeof(Vector3i)) + return UnsafeAsT(ConvertToVector3i(variant)); + + if (typeof(T) == typeof(Basis)) + return UnsafeAsT(ConvertToBasis(variant)); + + if (typeof(T) == typeof(Quaternion)) + return UnsafeAsT(ConvertToQuaternion(variant)); + + if (typeof(T) == typeof(Transform3D)) + return UnsafeAsT(ConvertToTransform3D(variant)); + + if (typeof(T) == typeof(Vector4)) + return UnsafeAsT(ConvertToVector4(variant)); + + if (typeof(T) == typeof(Vector4i)) + return UnsafeAsT(ConvertToVector4i(variant)); + + if (typeof(T) == typeof(AABB)) + return UnsafeAsT(ConvertToAABB(variant)); + + if (typeof(T) == typeof(Color)) + return UnsafeAsT(ConvertToColor(variant)); + + if (typeof(T) == typeof(Plane)) + return UnsafeAsT(ConvertToPlane(variant)); + + if (typeof(T) == typeof(Callable)) + return UnsafeAsT(ConvertToCallableManaged(variant)); + + if (typeof(T) == typeof(SignalInfo)) + return UnsafeAsT(ConvertToSignalInfo(variant)); + + if (typeof(T) == typeof(string)) + return UnsafeAsT(ConvertToStringObject(variant)); + + if (typeof(T) == typeof(byte[])) + return UnsafeAsT(ConvertAsPackedByteArrayToSystemArray(variant)); + + if (typeof(T) == typeof(int[])) + return UnsafeAsT(ConvertAsPackedInt32ArrayToSystemArray(variant)); + + if (typeof(T) == typeof(long[])) + return UnsafeAsT(ConvertAsPackedInt64ArrayToSystemArray(variant)); + + if (typeof(T) == typeof(float[])) + return UnsafeAsT(ConvertAsPackedFloat32ArrayToSystemArray(variant)); + + if (typeof(T) == typeof(double[])) + return UnsafeAsT(ConvertAsPackedFloat64ArrayToSystemArray(variant)); + + if (typeof(T) == typeof(string[])) + return UnsafeAsT(ConvertAsPackedStringArrayToSystemArray(variant)); + + if (typeof(T) == typeof(Vector2[])) + return UnsafeAsT(ConvertAsPackedVector2ArrayToSystemArray(variant)); + + if (typeof(T) == typeof(Vector3[])) + return UnsafeAsT(ConvertAsPackedVector3ArrayToSystemArray(variant)); + + if (typeof(T) == typeof(Color[])) + return UnsafeAsT(ConvertAsPackedColorArrayToSystemArray(variant)); + + if (typeof(T) == typeof(StringName[])) + return UnsafeAsT(ConvertToSystemArrayOfStringName(variant)); + + if (typeof(T) == typeof(NodePath[])) + return UnsafeAsT(ConvertToSystemArrayOfNodePath(variant)); + + if (typeof(T) == typeof(RID[])) + return UnsafeAsT(ConvertToSystemArrayOfRID(variant)); + + if (typeof(T) == typeof(StringName)) + return UnsafeAsT(ConvertToStringNameObject(variant)); + + if (typeof(T) == typeof(NodePath)) + return UnsafeAsT(ConvertToNodePathObject(variant)); + + if (typeof(T) == typeof(RID)) + return UnsafeAsT(ConvertToRID(variant)); + + if (typeof(T) == typeof(Godot.Collections.Dictionary)) + return UnsafeAsT(ConvertToDictionaryObject(variant)); + + if (typeof(T) == typeof(Godot.Collections.Array)) + return UnsafeAsT(ConvertToArrayObject(variant)); + + if (typeof(T) == typeof(Variant)) + return UnsafeAsT(Variant.CreateCopyingBorrowed(variant)); + + // More complex checks here at the end, to avoid screwing the simple ones in case they're not optimized away. + + // `typeof(X).IsAssignableFrom(typeof(T))` is optimized away + + if (typeof(Godot.Object).IsAssignableFrom(typeof(T))) + return (T)(object)ConvertToGodotObject(variant); + + // `typeof(T).IsValueType` is optimized away + // `typeof(T).IsEnum` is NOT optimized away: https://github.com/dotnet/runtime/issues/67113 + // Fortunately, `typeof(System.Enum).IsAssignableFrom(typeof(T))` does the job! + + if (typeof(T).IsValueType && typeof(System.Enum).IsAssignableFrom(typeof(T))) + { + // `Type.GetTypeCode(typeof(T).GetEnumUnderlyingType())` is not optimized away. + // Fortunately, `Unsafe.SizeOf<T>()` works and is optimized away. + // We don't need to know whether it's signed or unsigned. + + if (Unsafe.SizeOf<T>() == 1) + return UnsafeAsT(ConvertToInt8(variant)); + + if (Unsafe.SizeOf<T>() == 2) + return UnsafeAsT(ConvertToInt16(variant)); + + if (Unsafe.SizeOf<T>() == 4) + return UnsafeAsT(ConvertToInt32(variant)); + + if (Unsafe.SizeOf<T>() == 8) + return UnsafeAsT(ConvertToInt64(variant)); + + throw UnsupportedType<T>(); + } + + return GenericConversion<T>.FromVariant(variant); + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs index 664b2e0f34..42c6b0a37e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs @@ -22,7 +22,7 @@ namespace Godot /// <value>Equivalent to <see cref="x"/>, <see cref="y"/>, and <see cref="z"/>.</value> public Vector3 Normal { - get { return _normal; } + readonly get { return _normal; } set { _normal = value; } } @@ -32,7 +32,7 @@ namespace Godot /// <value>Equivalent to <see cref="Normal"/>'s X value.</value> public real_t x { - get + readonly get { return _normal.x; } @@ -48,7 +48,7 @@ namespace Godot /// <value>Equivalent to <see cref="Normal"/>'s Y value.</value> public real_t y { - get + readonly get { return _normal.y; } @@ -64,7 +64,7 @@ namespace Godot /// <value>Equivalent to <see cref="Normal"/>'s Z value.</value> public real_t z { - get + readonly get { return _normal.z; } @@ -90,7 +90,7 @@ namespace Godot /// <value>Equivalent to <see cref="Normal"/> multiplied by <see cref="D"/>.</value> public Vector3 Center { - get + readonly get { return _normal * D; } @@ -106,7 +106,7 @@ namespace Godot /// </summary> /// <param name="point">The position to use for the calculation.</param> /// <returns>The shortest distance.</returns> - public real_t DistanceTo(Vector3 point) + public readonly real_t DistanceTo(Vector3 point) { return _normal.Dot(point) - D; } @@ -118,7 +118,7 @@ namespace Godot /// <param name="point">The point to check.</param> /// <param name="tolerance">The tolerance threshold.</param> /// <returns>A <see langword="bool"/> for whether or not the plane has the point.</returns> - public bool HasPoint(Vector3 point, real_t tolerance = Mathf.Epsilon) + public readonly bool HasPoint(Vector3 point, real_t tolerance = Mathf.Epsilon) { real_t dist = _normal.Dot(point) - D; return Mathf.Abs(dist) <= tolerance; @@ -131,7 +131,7 @@ namespace Godot /// <param name="b">One of the three planes to use in the calculation.</param> /// <param name="c">One of the three planes to use in the calculation.</param> /// <returns>The intersection, or <see langword="null"/> if none is found.</returns> - public Vector3? Intersect3(Plane b, Plane c) + public readonly Vector3? Intersect3(Plane b, Plane c) { real_t denom = _normal.Cross(b._normal).Dot(c._normal); @@ -155,7 +155,7 @@ namespace Godot /// <param name="from">The start of the ray.</param> /// <param name="dir">The direction of the ray, normalized.</param> /// <returns>The intersection, or <see langword="null"/> if none is found.</returns> - public Vector3? IntersectRay(Vector3 from, Vector3 dir) + public readonly Vector3? IntersectRay(Vector3 from, Vector3 dir) { real_t den = _normal.Dot(dir); @@ -183,7 +183,7 @@ namespace Godot /// <param name="begin">The start of the line segment.</param> /// <param name="end">The end of the line segment.</param> /// <returns>The intersection, or <see langword="null"/> if none is found.</returns> - public Vector3? IntersectSegment(Vector3 begin, Vector3 end) + public readonly Vector3? IntersectSegment(Vector3 begin, Vector3 end) { Vector3 segment = begin - end; real_t den = _normal.Dot(segment); @@ -209,7 +209,7 @@ namespace Godot /// </summary> /// <param name="point">The point to check.</param> /// <returns>A <see langword="bool"/> for whether or not the point is above the plane.</returns> - public bool IsPointOver(Vector3 point) + public readonly bool IsPointOver(Vector3 point) { return _normal.Dot(point) > D; } @@ -218,7 +218,7 @@ namespace Godot /// Returns the plane scaled to unit length. /// </summary> /// <returns>A normalized version of the plane.</returns> - public Plane Normalized() + public readonly Plane Normalized() { real_t len = _normal.Length(); @@ -235,7 +235,7 @@ namespace Godot /// </summary> /// <param name="point">The point to project.</param> /// <returns>The projected point.</returns> - public Vector3 Project(Vector3 point) + public readonly Vector3 Project(Vector3 point) { return point - (_normal * DistanceTo(point)); } @@ -363,7 +363,7 @@ namespace Godot /// </summary> /// <param name="obj">The other object to compare.</param> /// <returns>Whether or not the plane and the other object are exactly equal.</returns> - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { return obj is Plane other && Equals(other); } @@ -373,7 +373,7 @@ namespace Godot /// </summary> /// <param name="other">The other plane to compare.</param> /// <returns>Whether or not the planes are exactly equal.</returns> - public bool Equals(Plane other) + public readonly bool Equals(Plane other) { return _normal == other._normal && D == other.D; } @@ -384,7 +384,7 @@ namespace Godot /// </summary> /// <param name="other">The other plane to compare.</param> /// <returns>Whether or not the planes are approximately equal.</returns> - public bool IsEqualApprox(Plane other) + public readonly bool IsEqualApprox(Plane other) { return _normal.IsEqualApprox(other._normal) && Mathf.IsEqualApprox(D, other.D); } @@ -393,7 +393,7 @@ namespace Godot /// Serves as the hash function for <see cref="Plane"/>. /// </summary> /// <returns>A hash code for this plane.</returns> - public override int GetHashCode() + public override readonly int GetHashCode() { return _normal.GetHashCode() ^ D.GetHashCode(); } @@ -402,7 +402,7 @@ namespace Godot /// Converts this <see cref="Plane"/> to a string. /// </summary> /// <returns>A string representation of this plane.</returns> - public override string ToString() + public override readonly string ToString() { return $"{_normal}, {D}"; } @@ -411,7 +411,7 @@ namespace Godot /// Converts this <see cref="Plane"/> to a string with the given <paramref name="format"/>. /// </summary> /// <returns>A string representation of this plane.</returns> - public string ToString(string format) + public readonly string ToString(string format) { return $"{_normal.ToString(format)}, {D.ToString(format)}"; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs index da895fd121..8b1b73fcc3 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs @@ -3,6 +3,14 @@ using System.Runtime.InteropServices; namespace Godot { + /// <summary> + /// A 4x4 matrix used for 3D projective transformations. It can represent transformations such as + /// translation, rotation, scaling, shearing, and perspective division. It consists of four + /// <see cref="Vector4"/> columns. + /// For purely linear transformations (translation, rotation, and scale), it is recommended to use + /// <see cref="Transform3D"/>, as it is more performant and has a lower memory footprint. + /// Used internally as <see cref="Camera3D"/>'s projection matrix. + /// </summary> [Serializable] [StructLayout(LayoutKind.Sequential)] public struct Projection : IEquatable<Projection> @@ -59,48 +67,107 @@ namespace Godot public Vector4 w; /// <summary> - /// Constructs a projection from 4 vectors (matrix columns). + /// Access whole columns in the form of <see cref="Vector4"/>. /// </summary> - /// <param name="x">The X column, or column index 0.</param> - /// <param name="y">The Y column, or column index 1.</param> - /// <param name="z">The Z column, or column index 2.</param> - /// <param name="w">The W column, or column index 3.</param> - public Projection(Vector4 x, Vector4 y, Vector4 z, Vector4 w) + /// <param name="column">Which column vector.</param> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="column"/> is not 0, 1, 2 or 3. + /// </exception> + public Vector4 this[int column] { - this.x = x; - this.y = y; - this.z = z; - this.w = w; + readonly get + { + switch (column) + { + case 0: + return x; + case 1: + return y; + case 2: + return z; + case 3: + return w; + default: + throw new ArgumentOutOfRangeException(nameof(column)); + } + } + set + { + switch (column) + { + case 0: + x = value; + return; + case 1: + y = value; + return; + case 2: + z = value; + return; + case 3: + w = value; + return; + default: + throw new ArgumentOutOfRangeException(nameof(column)); + } + } } /// <summary> - /// Constructs a new <see cref="Projection"/> from a <see cref="Transform3D"/>. + /// Access single values. /// </summary> - /// <param name="transform">The <see cref="Transform3D"/>.</param> - public Projection(Transform3D transform) + /// <param name="column">Which column vector.</param> + /// <param name="row">Which row of the column.</param> + /// <exception cref="ArgumentOutOfRangeException"> + /// <paramref name="column"/> or <paramref name="row"/> are not 0, 1, 2 or 3. + /// </exception> + public real_t this[int column, int row] { - x = new Vector4(transform.basis.Row0.x, transform.basis.Row1.x, transform.basis.Row2.x, 0); - y = new Vector4(transform.basis.Row0.y, transform.basis.Row1.y, transform.basis.Row2.y, 0); - z = new Vector4(transform.basis.Row0.z, transform.basis.Row1.z, transform.basis.Row2.z, 0); - w = new Vector4(transform.origin.x, transform.origin.y, transform.origin.z, 1); + readonly get + { + switch (column) + { + case 0: + return x[row]; + case 1: + return y[row]; + case 2: + return z[row]; + case 3: + return w[row]; + default: + throw new ArgumentOutOfRangeException(nameof(column)); + } + } + set + { + switch (column) + { + case 0: + x[row] = value; + return; + case 1: + y[row] = value; + return; + case 2: + z[row] = value; + return; + case 3: + w[row] = value; + return; + default: + throw new ArgumentOutOfRangeException(nameof(column)); + } + } } /// <summary> - /// Constructs a new <see cref="Transform3D"/> from the <see cref="Projection"/>. + /// Creates a new <see cref="Projection"/> that projects positions from a depth range of + /// <c>-1</c> to <c>1</c> to one that ranges from <c>0</c> to <c>1</c>, and flips the projected + /// positions vertically, according to <paramref name="flipY"/>. /// </summary> - /// <param name="proj">The <see cref="Projection"/>.</param> - public static explicit operator Transform3D(Projection proj) - { - return new Transform3D( - new Basis( - new Vector3(proj.x.x, proj.x.y, proj.x.z), - new Vector3(proj.y.x, proj.y.y, proj.y.z), - new Vector3(proj.z.x, proj.z.y, proj.z.z) - ), - new Vector3(proj.w.x, proj.w.y, proj.w.z) - ); - } - + /// <param name="flipY">If the projection should be flipped vertically.</param> + /// <returns>The created projection.</returns> public static Projection CreateDepthCorrection(bool flipY) { return new Projection( @@ -111,6 +178,12 @@ namespace Godot ); } + /// <summary> + /// Creates a new <see cref="Projection"/> that scales a given projection to fit around + /// a given <see cref="AABB"/> in projection space. + /// </summary> + /// <param name="aabb">The AABB to fit the projection around.</param> + /// <returns>The created projection.</returns> public static Projection CreateFitAabb(AABB aabb) { Vector3 min = aabb.Position; @@ -124,6 +197,25 @@ namespace Godot ); } + /// <summary> + /// Creates a new <see cref="Projection"/> for projecting positions onto a head-mounted display with + /// the given X:Y aspect ratio, distance between eyes, display width, distance to lens, oversampling factor, + /// and depth clipping planes. + /// <paramref name="eye"/> creates the projection for the left eye when set to 1, + /// or the right eye when set to 2. + /// </summary> + /// <param name="eye"> + /// The eye to create the projection for. + /// The left eye when set to 1, the right eye when set to 2. + /// </param> + /// <param name="aspect">The aspect ratio.</param> + /// <param name="intraocularDist">The distance between the eyes.</param> + /// <param name="displayWidth">The display width.</param> + /// <param name="displayToLens">The distance to the lens.</param> + /// <param name="oversample">The oversampling factor.</param> + /// <param name="zNear">The near clipping distance.</param> + /// <param name="zFar">The far clipping distance.</param> + /// <returns>The created projection.</returns> public static Projection CreateForHmd(int eye, real_t aspect, real_t intraocularDist, real_t displayWidth, real_t displayToLens, real_t oversample, real_t zNear, real_t zFar) { real_t f1 = (intraocularDist * (real_t)0.5) / displayToLens; @@ -148,6 +240,17 @@ namespace Godot } } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions in a frustum with + /// the given clipping planes. + /// </summary> + /// <param name="left">The left clipping distance.</param> + /// <param name="right">The right clipping distance.</param> + /// <param name="bottom">The bottom clipping distance.</param> + /// <param name="top">The top clipping distance.</param> + /// <param name="near">The near clipping distance.</param> + /// <param name="far">The far clipping distance.</param> + /// <returns>The created projection.</returns> public static Projection CreateFrustum(real_t left, real_t right, real_t bottom, real_t top, real_t near, real_t far) { if (right <= left) @@ -179,6 +282,18 @@ namespace Godot ); } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions in a frustum with + /// the given size, X:Y aspect ratio, offset, and clipping planes. + /// <paramref name="flipFov"/> determines whether the projection's field of view is flipped over its diagonal. + /// </summary> + /// <param name="size">The frustum size.</param> + /// <param name="aspect">The aspect ratio.</param> + /// <param name="offset">The offset to apply.</param> + /// <param name="near">The near clipping distance.</param> + /// <param name="far">The far clipping distance.</param> + /// <param name="flipFov">If the field of view is flipped over the projection's diagonal.</param> + /// <returns>The created projection.</returns> public static Projection CreateFrustumAspect(real_t size, real_t aspect, Vector2 offset, real_t near, real_t far, bool flipFov) { if (!flipFov) @@ -188,6 +303,11 @@ namespace Godot return CreateFrustum(-size / 2 + offset.x, +size / 2 + offset.x, -size / aspect / 2 + offset.y, +size / aspect / 2 + offset.y, near, far); } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions into the given <see cref="Rect2"/>. + /// </summary> + /// <param name="rect">The Rect2 to project positions into.</param> + /// <returns>The created projection.</returns> public static Projection CreateLightAtlasRect(Rect2 rect) { return new Projection( @@ -198,6 +318,17 @@ namespace Godot ); } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions using an orthogonal projection with + /// the given clipping planes. + /// </summary> + /// <param name="left">The left clipping distance.</param> + /// <param name="right">The right clipping distance.</param> + /// <param name="bottom">The bottom clipping distance.</param> + /// <param name="top">The top clipping distance.</param> + /// <param name="zNear">The near clipping distance.</param> + /// <param name="zFar">The far clipping distance.</param> + /// <returns>The created projection.</returns> public static Projection CreateOrthogonal(real_t left, real_t right, real_t bottom, real_t top, real_t zNear, real_t zFar) { Projection proj = Projection.Identity; @@ -211,6 +342,17 @@ namespace Godot return proj; } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions using an orthogonal projection with + /// the given size, X:Y aspect ratio, and clipping planes. + /// <paramref name="flipFov"/> determines whether the projection's field of view is flipped over its diagonal. + /// </summary> + /// <param name="size">The frustum size.</param> + /// <param name="aspect">The aspect ratio.</param> + /// <param name="zNear">The near clipping distance.</param> + /// <param name="zFar">The far clipping distance.</param> + /// <param name="flipFov">If the field of view is flipped over the projection's diagonal.</param> + /// <returns>The created projection.</returns> public static Projection CreateOrthogonalAspect(real_t size, real_t aspect, real_t zNear, real_t zFar, bool flipFov) { if (!flipFov) @@ -220,6 +362,17 @@ namespace Godot return CreateOrthogonal(-size / 2, +size / 2, -size / aspect / 2, +size / aspect / 2, zNear, zFar); } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions using a perspective projection with + /// the given Y-axis field of view (in degrees), X:Y aspect ratio, and clipping planes. + /// <paramref name="flipFov"/> determines whether the projection's field of view is flipped over its diagonal. + /// </summary> + /// <param name="fovyDegrees">The vertical field of view (in degrees).</param> + /// <param name="aspect">The aspect ratio.</param> + /// <param name="zNear">The near clipping distance.</param> + /// <param name="zFar">The far clipping distance.</param> + /// <param name="flipFov">If the field of view is flipped over the projection's diagonal.</param> + /// <returns>The created projection.</returns> public static Projection CreatePerspective(real_t fovyDegrees, real_t aspect, real_t zNear, real_t zFar, bool flipFov) { if (flipFov) @@ -249,6 +402,27 @@ namespace Godot return proj; } + /// <summary> + /// Creates a new <see cref="Projection"/> that projects positions using a perspective projection with + /// the given Y-axis field of view (in degrees), X:Y aspect ratio, and clipping distances. + /// The projection is adjusted for a head-mounted display with the given distance between eyes and distance + /// to a point that can be focused on. + /// <paramref name="eye"/> creates the projection for the left eye when set to 1, + /// or the right eye when set to 2. + /// <paramref name="flipFov"/> determines whether the projection's field of view is flipped over its diagonal. + /// </summary> + /// <param name="fovyDegrees">The vertical field of view (in degrees).</param> + /// <param name="aspect">The aspect ratio.</param> + /// <param name="zNear">The near clipping distance.</param> + /// <param name="zFar">The far clipping distance.</param> + /// <param name="flipFov">If the field of view is flipped over the projection's diagonal.</param> + /// <param name="eye"> + /// The eye to create the projection for. + /// The left eye when set to 1, the right eye when set to 2. + /// </param> + /// <param name="intraocularDist">The distance between the eyes.</param> + /// <param name="convergenceDist">The distance to a point of convergence that can be focused on.</param> + /// <returns>The created projection.</returns> public static Projection CreatePerspectiveHmd(real_t fovyDegrees, real_t aspect, real_t zNear, real_t zFar, bool flipFov, int eye, real_t intraocularDist, real_t convergenceDist) { if (flipFov) @@ -286,7 +460,14 @@ namespace Godot return proj * cm; } - public real_t Determinant() + /// <summary> + /// Returns a scalar value that is the signed factor by which areas are scaled by this matrix. + /// If the sign is negative, the matrix flips the orientation of the area. + /// The determinant can be used to calculate the invertibility of a matrix or solve linear systems + /// of equations involving the matrix, among other applications. + /// </summary> + /// <returns>The determinant calculated from this projection.</returns> + public readonly real_t Determinant() { return x.w * y.z * z.y * w.x - x.z * y.w * z.y * w.x - x.w * y.y * z.z * w.x + x.y * y.w * z.z * w.x + @@ -302,13 +483,21 @@ namespace Godot x.y * y.x * z.z * w.w + x.x * y.y * z.z * w.w; } - public real_t GetAspect() + /// <summary> + /// Returns the X:Y aspect ratio of this <see cref="Projection"/>'s viewport. + /// </summary> + /// <returns>The aspect ratio from this projection's viewport.</returns> + public readonly real_t GetAspect() { Vector2 vpHe = GetViewportHalfExtents(); return vpHe.x / vpHe.y; } - public real_t GetFov() + /// <summary> + /// Returns the horizontal field of view of the projection (in degrees). + /// </summary> + /// <returns>The horizontal field of view of this projection.</returns> + public readonly real_t GetFov() { Plane rightPlane = new Plane(x.w - x.x, y.w - y.x, z.w - z.x, -w.w + w.x).Normalized(); if (z.x == 0 && z.y == 0) @@ -322,12 +511,23 @@ namespace Godot } } + /// <summary> + /// Returns the vertical field of view of the projection (in degrees) associated with + /// the given horizontal field of view (in degrees) and aspect ratio. + /// </summary> + /// <param name="fovx">The horizontal field of view (in degrees).</param> + /// <param name="aspect">The aspect ratio.</param> + /// <returns>The vertical field of view of this projection.</returns> public static real_t GetFovy(real_t fovx, real_t aspect) { return Mathf.RadToDeg(Mathf.Atan(aspect * Mathf.Tan(Mathf.DegToRad(fovx) * (real_t)0.5)) * (real_t)2.0); } - public real_t GetLodMultiplier() + /// <summary> + /// Returns the factor by which the visible level of detail is scaled by this <see cref="Projection"/>. + /// </summary> + /// <returns>The level of detail factor for this projection.</returns> + public readonly real_t GetLodMultiplier() { if (IsOrthogonal()) { @@ -341,14 +541,29 @@ namespace Godot } } - public int GetPixelsPerMeter(int forPixelWidth) + /// <summary> + /// Returns the number of pixels with the given pixel width displayed per meter, after + /// this <see cref="Projection"/> is applied. + /// </summary> + /// <param name="forPixelWidth">The width for each pixel (in meters).</param> + /// <returns>The number of pixels per meter.</returns> + public readonly int GetPixelsPerMeter(int forPixelWidth) { Vector3 result = this * new Vector3(1, 0, -1); return (int)((result.x * (real_t)0.5 + (real_t)0.5) * forPixelWidth); } - public Plane GetProjectionPlane(Planes plane) + /// <summary> + /// Returns the clipping plane of this <see cref="Projection"/> whose index is given + /// by <paramref name="plane"/>. + /// <paramref name="plane"/> should be equal to one of <see cref="Planes.Near"/>, + /// <see cref="Planes.Far"/>, <see cref="Planes.Left"/>, <see cref="Planes.Top"/>, + /// <see cref="Planes.Right"/>, or <see cref="Planes.Bottom"/>. + /// </summary> + /// <param name="plane">The kind of clipping plane to get from the projection.</param> + /// <returns>The clipping plane of this projection.</returns> + public readonly Plane GetProjectionPlane(Planes plane) { Plane newPlane = plane switch { @@ -364,36 +579,64 @@ namespace Godot return newPlane.Normalized(); } - public Vector2 GetFarPlaneHalfExtents() + /// <summary> + /// Returns the dimensions of the far clipping plane of the projection, divided by two. + /// </summary> + /// <returns>The half extents for this projection's far plane.</returns> + public readonly Vector2 GetFarPlaneHalfExtents() { var res = GetProjectionPlane(Planes.Far).Intersect3(GetProjectionPlane(Planes.Right), GetProjectionPlane(Planes.Top)); return new Vector2(res.Value.x, res.Value.y); } - public Vector2 GetViewportHalfExtents() + /// <summary> + /// Returns the dimensions of the viewport plane that this <see cref="Projection"/> + /// projects positions onto, divided by two. + /// </summary> + /// <returns>The half extents for this projection's viewport plane.</returns> + public readonly Vector2 GetViewportHalfExtents() { var res = GetProjectionPlane(Planes.Near).Intersect3(GetProjectionPlane(Planes.Right), GetProjectionPlane(Planes.Top)); return new Vector2(res.Value.x, res.Value.y); } - public real_t GetZFar() + /// <summary> + /// Returns the distance for this <see cref="Projection"/> beyond which positions are clipped. + /// </summary> + /// <returns>The distance beyond which positions are clipped.</returns> + public readonly real_t GetZFar() { return GetProjectionPlane(Planes.Far).D; } - public real_t GetZNear() + /// <summary> + /// Returns the distance for this <see cref="Projection"/> before which positions are clipped. + /// </summary> + /// <returns>The distance before which positions are clipped.</returns> + public readonly real_t GetZNear() { return -GetProjectionPlane(Planes.Near).D; } - public Projection FlippedY() + /// <summary> + /// Returns a copy of this <see cref="Projection"/> with the signs of the values of the Y column flipped. + /// </summary> + /// <returns>The flipped projection.</returns> + public readonly Projection FlippedY() { Projection proj = this; proj.y = -proj.y; return proj; } - public Projection PerspectiveZNearAdjusted(real_t newZNear) + /// <summary> + /// Returns a <see cref="Projection"/> with the near clipping distance adjusted to be + /// <paramref name="newZNear"/>. + /// Note: The original <see cref="Projection"/> must be a perspective projection. + /// </summary> + /// <param name="newZNear">The near clipping distance to adjust the projection to.</param> + /// <returns>The adjusted projection.</returns> + public readonly Projection PerspectiveZNearAdjusted(real_t newZNear) { Projection proj = this; real_t zFar = GetZFar(); @@ -404,7 +647,13 @@ namespace Godot return proj; } - public Projection JitterOffseted(Vector2 offset) + /// <summary> + /// Returns a <see cref="Projection"/> with the X and Y values from the given <see cref="Vector2"/> + /// added to the first and second values of the final column respectively. + /// </summary> + /// <param name="offset">The offset to apply to the projection.</param> + /// <returns>The offseted projection.</returns> + public readonly Projection JitterOffseted(Vector2 offset) { Projection proj = this; proj.w.x += offset.x; @@ -412,7 +661,12 @@ namespace Godot return proj; } - public Projection Inverse() + /// <summary> + /// Returns a <see cref="Projection"/> that performs the inverse of this <see cref="Projection"/>'s + /// projective transformation. + /// </summary> + /// <returns>The inverted projection.</returns> + public readonly Projection Inverse() { Projection proj = this; int i, j, k; @@ -535,11 +789,70 @@ namespace Godot return proj; } - public bool IsOrthogonal() + /// <summary> + /// Returns <see langword="true"/> if this <see cref="Projection"/> performs an orthogonal projection. + /// </summary> + /// <returns>If the projection performs an orthogonal projection.</returns> + public readonly bool IsOrthogonal() { return w.w == (real_t)1.0; } + // Constants + private static readonly Projection _zero = new Projection( + new Vector4(0, 0, 0, 0), + new Vector4(0, 0, 0, 0), + new Vector4(0, 0, 0, 0), + new Vector4(0, 0, 0, 0) + ); + private static readonly Projection _identity = new Projection( + new Vector4(1, 0, 0, 0), + new Vector4(0, 1, 0, 0), + new Vector4(0, 0, 1, 0), + new Vector4(0, 0, 0, 1) + ); + + /// <summary> + /// Zero projection, a projection with all components set to <c>0</c>. + /// </summary> + /// <value>Equivalent to <c>new Projection(Vector4.Zero, Vector4.Zero, Vector4.Zero, Vector4.Zero)</c>.</value> + public static Projection Zero { get { return _zero; } } + + /// <summary> + /// The identity projection, with no distortion applied. + /// This is used as a replacement for <c>Projection()</c> in GDScript. + /// Do not use <c>new Projection()</c> with no arguments in C#, because it sets all values to zero. + /// </summary> + /// <value>Equivalent to <c>new Projection(new Vector4(1, 0, 0, 0), new Vector4(0, 1, 0, 0), new Vector4(0, 0, 1, 0), new Vector4(0, 0, 0, 1))</c>.</value> + public static Projection Identity { get { return _identity; } } + + /// <summary> + /// Constructs a projection from 4 vectors (matrix columns). + /// </summary> + /// <param name="x">The X column, or column index 0.</param> + /// <param name="y">The Y column, or column index 1.</param> + /// <param name="z">The Z column, or column index 2.</param> + /// <param name="w">The W column, or column index 3.</param> + public Projection(Vector4 x, Vector4 y, Vector4 z, Vector4 w) + { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + /// <summary> + /// Constructs a new <see cref="Projection"/> from a <see cref="Transform3D"/>. + /// </summary> + /// <param name="transform">The <see cref="Transform3D"/>.</param> + public Projection(Transform3D transform) + { + x = new Vector4(transform.basis.Row0.x, transform.basis.Row1.x, transform.basis.Row2.x, 0); + y = new Vector4(transform.basis.Row0.y, transform.basis.Row1.y, transform.basis.Row2.y, 0); + z = new Vector4(transform.basis.Row0.z, transform.basis.Row1.z, transform.basis.Row2.z, 0); + w = new Vector4(transform.origin.x, transform.origin.y, transform.origin.z, 1); + } + /// <summary> /// Composes these two projections by multiplying them /// together. This has the effect of applying the right @@ -646,133 +959,47 @@ namespace Godot } /// <summary> - /// Access whole columns in the form of <see cref="Vector4"/>. + /// Constructs a new <see cref="Transform3D"/> from the <see cref="Projection"/>. /// </summary> - /// <param name="column">Which column vector.</param> - /// <exception cref="ArgumentOutOfRangeException"> - /// <paramref name="column"/> is not 0, 1, 2 or 3. - /// </exception> - public Vector4 this[int column] + /// <param name="proj">The <see cref="Projection"/>.</param> + public static explicit operator Transform3D(Projection proj) { - get - { - switch (column) - { - case 0: - return x; - case 1: - return y; - case 2: - return z; - case 3: - return w; - default: - throw new ArgumentOutOfRangeException(nameof(column)); - } - } - set - { - switch (column) - { - case 0: - x = value; - return; - case 1: - y = value; - return; - case 2: - z = value; - return; - case 3: - w = value; - return; - default: - throw new ArgumentOutOfRangeException(nameof(column)); - } - } + return new Transform3D( + new Basis( + new Vector3(proj.x.x, proj.x.y, proj.x.z), + new Vector3(proj.y.x, proj.y.y, proj.y.z), + new Vector3(proj.z.x, proj.z.y, proj.z.z) + ), + new Vector3(proj.w.x, proj.w.y, proj.w.z) + ); } /// <summary> - /// Access single values. + /// Returns <see langword="true"/> if the projection is exactly equal + /// to the given object (<see paramref="obj"/>). /// </summary> - /// <param name="column">Which column vector.</param> - /// <param name="row">Which row of the column.</param> - /// <exception cref="ArgumentOutOfRangeException"> - /// <paramref name="column"/> or <paramref name="row"/> are not 0, 1, 2 or 3. - /// </exception> - public real_t this[int column, int row] + /// <param name="obj">The object to compare with.</param> + /// <returns>Whether or not the vector and the object are equal.</returns> + public override readonly bool Equals(object obj) { - get - { - switch (column) - { - case 0: - return x[row]; - case 1: - return y[row]; - case 2: - return z[row]; - case 3: - return w[row]; - default: - throw new ArgumentOutOfRangeException(nameof(column)); - } - } - set - { - switch (column) - { - case 0: - x[row] = value; - return; - case 1: - y[row] = value; - return; - case 2: - z[row] = value; - return; - case 3: - w[row] = value; - return; - default: - throw new ArgumentOutOfRangeException(nameof(column)); - } - } + return obj is Projection other && Equals(other); } - // Constants - private static readonly Projection _zero = new Projection( - new Vector4(0, 0, 0, 0), - new Vector4(0, 0, 0, 0), - new Vector4(0, 0, 0, 0), - new Vector4(0, 0, 0, 0) - ); - private static readonly Projection _identity = new Projection( - new Vector4(1, 0, 0, 0), - new Vector4(0, 1, 0, 0), - new Vector4(0, 0, 1, 0), - new Vector4(0, 0, 0, 1) - ); - /// <summary> - /// Zero projection, a projection with all components set to <c>0</c>. - /// </summary> - /// <value>Equivalent to <c>new Projection(Vector4.Zero, Vector4.Zero, Vector4.Zero, Vector4.Zero)</c>.</value> - public static Projection Zero { get { return _zero; } } - - /// <summary> - /// The identity projection, with no distortion applied. - /// This is used as a replacement for <c>Projection()</c> in GDScript. - /// Do not use <c>new Projection()</c> with no arguments in C#, because it sets all values to zero. + /// Returns <see langword="true"/> if the projections are exactly equal. /// </summary> - /// <value>Equivalent to <c>new Projection(new Vector4(1, 0, 0, 0), new Vector4(0, 1, 0, 0), new Vector4(0, 0, 1, 0), new Vector4(0, 0, 0, 1))</c>.</value> - public static Projection Identity { get { return _identity; } } + /// <param name="other">The other projection.</param> + /// <returns>Whether or not the projections are exactly equal.</returns> + public readonly bool Equals(Projection other) + { + return x == other.x && y == other.y && z == other.z && w == other.w; + } /// <summary> /// Serves as the hash function for <see cref="Projection"/>. /// </summary> /// <returns>A hash code for this projection.</returns> - public override int GetHashCode() + public override readonly int GetHashCode() { return y.GetHashCode() ^ x.GetHashCode() ^ z.GetHashCode() ^ w.GetHashCode(); } @@ -781,7 +1008,7 @@ namespace Godot /// Converts this <see cref="Projection"/> to a string. /// </summary> /// <returns>A string representation of this projection.</returns> - public override string ToString() + public override readonly string ToString() { return $"{x.x}, {x.y}, {x.z}, {x.w}\n{y.x}, {y.y}, {y.z}, {y.w}\n{z.x}, {z.y}, {z.z}, {z.w}\n{w.x}, {w.y}, {w.z}, {w.w}\n"; } @@ -790,33 +1017,12 @@ namespace Godot /// Converts this <see cref="Projection"/> to a string with the given <paramref name="format"/>. /// </summary> /// <returns>A string representation of this projection.</returns> - public string ToString(string format) + public readonly string ToString(string format) { return $"{x.x.ToString(format)}, {x.y.ToString(format)}, {x.z.ToString(format)}, {x.w.ToString(format)}\n" + $"{y.x.ToString(format)}, {y.y.ToString(format)}, {y.z.ToString(format)}, {y.w.ToString(format)}\n" + $"{z.x.ToString(format)}, {z.y.ToString(format)}, {z.z.ToString(format)}, {z.w.ToString(format)}\n" + $"{w.x.ToString(format)}, {w.y.ToString(format)}, {w.z.ToString(format)}, {w.w.ToString(format)}\n"; } - - /// <summary> - /// Returns <see langword="true"/> if the projection is exactly equal - /// to the given object (<see paramref="obj"/>). - /// </summary> - /// <param name="obj">The object to compare with.</param> - /// <returns>Whether or not the vector and the object are equal.</returns> - public override bool Equals(object obj) - { - return obj is Projection other && Equals(other); - } - - /// <summary> - /// Returns <see langword="true"/> if the projections are exactly equal. - /// </summary> - /// <param name="other">The other projection.</param> - /// <returns>Whether or not the projections are exactly equal.</returns> - public bool Equals(Projection other) - { - return x == other.x && y == other.y && z == other.z && w == other.w; - } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs index f01f0be985..c55003586b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs @@ -58,7 +58,7 @@ namespace Godot /// </value> public real_t this[int index] { - get + readonly get { switch (index) { @@ -101,7 +101,7 @@ namespace Godot /// </summary> /// <seealso cref="LengthSquared"/> /// <value>Equivalent to <c>Mathf.Sqrt(LengthSquared)</c>.</value> - public real_t Length + public readonly real_t Length { get { return Mathf.Sqrt(LengthSquared); } } @@ -112,7 +112,7 @@ namespace Godot /// you need to compare quaternions or need the squared length for some formula. /// </summary> /// <value>Equivalent to <c>Dot(this)</c>.</value> - public real_t LengthSquared + public readonly real_t LengthSquared { get { return Dot(this); } } @@ -128,7 +128,7 @@ namespace Godot /// </summary> /// <param name="to">The other quaternion.</param> /// <returns>The angle between the quaternions.</returns> - public real_t AngleTo(Quaternion to) + public readonly real_t AngleTo(Quaternion to) { real_t dot = Dot(to); return Mathf.Acos(Mathf.Clamp(dot * dot * 2 - 1, -1, 1)); @@ -143,7 +143,7 @@ namespace Godot /// <param name="postB">A quaternion after <paramref name="b"/>.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The interpolated quaternion.</returns> - public Quaternion SphericalCubicInterpolate(Quaternion b, Quaternion preA, Quaternion postB, real_t weight) + public readonly Quaternion SphericalCubicInterpolate(Quaternion b, Quaternion preA, Quaternion postB, real_t weight) { #if DEBUG if (!IsNormalized()) @@ -212,7 +212,7 @@ namespace Godot /// <param name="preAT"></param> /// <param name="postBT"></param> /// <returns>The interpolated quaternion.</returns> - public Quaternion SphericalCubicInterpolateInTime(Quaternion b, Quaternion preA, Quaternion postB, real_t weight, real_t bT, real_t preAT, real_t postBT) + public readonly Quaternion SphericalCubicInterpolateInTime(Quaternion b, Quaternion preA, Quaternion postB, real_t weight, real_t bT, real_t preAT, real_t postBT) { #if DEBUG if (!IsNormalized()) @@ -272,12 +272,12 @@ namespace Godot /// </summary> /// <param name="b">The other quaternion.</param> /// <returns>The dot product.</returns> - public real_t Dot(Quaternion b) + public readonly real_t Dot(Quaternion b) { return (x * b.x) + (y * b.y) + (z * b.z) + (w * b.w); } - public Quaternion Exp() + public readonly Quaternion Exp() { Vector3 v = new Vector3(x, y, z); real_t theta = v.Length(); @@ -289,12 +289,12 @@ namespace Godot return new Quaternion(v, theta); } - public real_t GetAngle() + public readonly real_t GetAngle() { return 2 * Mathf.Acos(w); } - public Vector3 GetAxis() + public readonly Vector3 GetAxis() { if (Mathf.Abs(w) > 1 - Mathf.Epsilon) { @@ -312,7 +312,7 @@ namespace Godot /// the rotation angles in the format (X angle, Y angle, Z angle). /// </summary> /// <returns>The Euler angle representation of this quaternion.</returns> - public Vector3 GetEuler(EulerOrder order = EulerOrder.Yxz) + public readonly Vector3 GetEuler(EulerOrder order = EulerOrder.Yxz) { #if DEBUG if (!IsNormalized()) @@ -328,7 +328,7 @@ namespace Godot /// Returns the inverse of the quaternion. /// </summary> /// <returns>The inverse quaternion.</returns> - public Quaternion Inverse() + public readonly Quaternion Inverse() { #if DEBUG if (!IsNormalized()) @@ -343,12 +343,12 @@ namespace Godot /// Returns whether the quaternion is normalized or not. /// </summary> /// <returns>A <see langword="bool"/> for whether the quaternion is normalized or not.</returns> - public bool IsNormalized() + public readonly bool IsNormalized() { return Mathf.Abs(LengthSquared - 1) <= Mathf.Epsilon; } - public Quaternion Log() + public readonly Quaternion Log() { Vector3 v = GetAxis() * GetAngle(); return new Quaternion(v.x, v.y, v.z, 0); @@ -358,7 +358,7 @@ namespace Godot /// Returns a copy of the quaternion, normalized to unit length. /// </summary> /// <returns>The normalized quaternion.</returns> - public Quaternion Normalized() + public readonly Quaternion Normalized() { return this / Length; } @@ -372,7 +372,7 @@ namespace Godot /// <param name="to">The destination quaternion for interpolation. Must be normalized.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The resulting quaternion of the interpolation.</returns> - public Quaternion Slerp(Quaternion to, real_t weight) + public readonly Quaternion Slerp(Quaternion to, real_t weight) { #if DEBUG if (!IsNormalized()) @@ -437,7 +437,7 @@ namespace Godot /// <param name="to">The destination quaternion for interpolation. Must be normalized.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The resulting quaternion of the interpolation.</returns> - public Quaternion Slerpni(Quaternion to, real_t weight) + public readonly Quaternion Slerpni(Quaternion to, real_t weight) { #if DEBUG if (!IsNormalized()) @@ -762,7 +762,7 @@ namespace Godot /// </summary> /// <param name="obj">The other object to compare.</param> /// <returns>Whether or not the quaternion and the other object are exactly equal.</returns> - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { return obj is Quaternion other && Equals(other); } @@ -772,7 +772,7 @@ namespace Godot /// </summary> /// <param name="other">The other quaternion to compare.</param> /// <returns>Whether or not the quaternions are exactly equal.</returns> - public bool Equals(Quaternion other) + public readonly bool Equals(Quaternion other) { return x == other.x && y == other.y && z == other.z && w == other.w; } @@ -783,7 +783,7 @@ namespace Godot /// </summary> /// <param name="other">The other quaternion to compare.</param> /// <returns>Whether or not the quaternions are approximately equal.</returns> - public bool IsEqualApprox(Quaternion other) + public readonly bool IsEqualApprox(Quaternion other) { return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y) && Mathf.IsEqualApprox(z, other.z) && Mathf.IsEqualApprox(w, other.w); } @@ -792,7 +792,7 @@ namespace Godot /// Serves as the hash function for <see cref="Quaternion"/>. /// </summary> /// <returns>A hash code for this quaternion.</returns> - public override int GetHashCode() + public override readonly int GetHashCode() { return y.GetHashCode() ^ x.GetHashCode() ^ z.GetHashCode() ^ w.GetHashCode(); } @@ -801,7 +801,7 @@ namespace Godot /// Converts this <see cref="Quaternion"/> to a string. /// </summary> /// <returns>A string representation of this quaternion.</returns> - public override string ToString() + public override readonly string ToString() { return $"({x}, {y}, {z}, {w})"; } @@ -810,7 +810,7 @@ namespace Godot /// Converts this <see cref="Quaternion"/> to a string with the given <paramref name="format"/>. /// </summary> /// <returns>A string representation of this quaternion.</returns> - public string ToString(string format) + public readonly string ToString(string format) { return $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)}, {w.ToString(format)})"; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs index a31fef8360..59b9faf16c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs @@ -12,9 +12,9 @@ namespace Godot /// classes such as <see cref="RenderingServer"/>. /// </summary> [StructLayout(LayoutKind.Sequential)] - public struct RID + public readonly struct RID { - private ulong _id; // Default is 0 + private readonly ulong _id; // Default is 0 internal RID(ulong id) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs index e80d75dacf..b0e0e75a34 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs @@ -20,7 +20,7 @@ namespace Godot /// <value>Directly uses a private field.</value> public Vector2 Position { - get { return _position; } + readonly get { return _position; } set { _position = value; } } @@ -31,7 +31,7 @@ namespace Godot /// <value>Directly uses a private field.</value> public Vector2 Size { - get { return _size; } + readonly get { return _size; } set { _size = value; } } @@ -45,7 +45,7 @@ namespace Godot /// </value> public Vector2 End { - get { return _position + _size; } + readonly get { return _position + _size; } set { _size = value - _position; } } @@ -53,7 +53,7 @@ namespace Godot /// The area of this <see cref="Rect2"/>. /// </summary> /// <value>Equivalent to <see cref="GetArea()"/>.</value> - public real_t Area + public readonly real_t Area { get { return GetArea(); } } @@ -63,7 +63,7 @@ namespace Godot /// the top-left corner is the origin and width and height are positive. /// </summary> /// <returns>The modified <see cref="Rect2"/>.</returns> - public Rect2 Abs() + public readonly Rect2 Abs() { Vector2 end = End; Vector2 topLeft = new Vector2(Mathf.Min(_position.x, end.x), Mathf.Min(_position.y, end.y)); @@ -79,7 +79,7 @@ namespace Godot /// The intersection of this <see cref="Rect2"/> and <paramref name="b"/>, /// or an empty <see cref="Rect2"/> if they do not intersect. /// </returns> - public Rect2 Intersection(Rect2 b) + public readonly Rect2 Intersection(Rect2 b) { Rect2 newRect = b; @@ -107,7 +107,7 @@ namespace Godot /// <returns> /// A <see langword="bool"/> for whether or not this <see cref="Rect2"/> encloses <paramref name="b"/>. /// </returns> - public bool Encloses(Rect2 b) + public readonly bool Encloses(Rect2 b) { return b._position.x >= _position.x && b._position.y >= _position.y && b._position.x + b._size.x < _position.x + _size.x && @@ -119,7 +119,7 @@ namespace Godot /// </summary> /// <param name="to">The point to include.</param> /// <returns>The expanded <see cref="Rect2"/>.</returns> - public Rect2 Expand(Vector2 to) + public readonly Rect2 Expand(Vector2 to) { Rect2 expanded = this; @@ -154,7 +154,7 @@ namespace Godot /// Returns the area of the <see cref="Rect2"/>. /// </summary> /// <returns>The area.</returns> - public real_t GetArea() + public readonly real_t GetArea() { return _size.x * _size.y; } @@ -164,7 +164,7 @@ namespace Godot /// to <see cref="Position"/> + (<see cref="Size"/> / 2). /// </summary> /// <returns>The center.</returns> - public Vector2 GetCenter() + public readonly Vector2 GetCenter() { return _position + (_size * 0.5f); } @@ -177,7 +177,7 @@ namespace Godot /// <seealso cref="GrowSide(Side, real_t)"/> /// <param name="by">The amount to grow by.</param> /// <returns>The grown <see cref="Rect2"/>.</returns> - public Rect2 Grow(real_t by) + public readonly Rect2 Grow(real_t by) { Rect2 g = this; @@ -200,7 +200,7 @@ namespace Godot /// <param name="right">The amount to grow by on the right side.</param> /// <param name="bottom">The amount to grow by on the bottom side.</param> /// <returns>The grown <see cref="Rect2"/>.</returns> - public Rect2 GrowIndividual(real_t left, real_t top, real_t right, real_t bottom) + public readonly Rect2 GrowIndividual(real_t left, real_t top, real_t right, real_t bottom) { Rect2 g = this; @@ -221,7 +221,7 @@ namespace Godot /// <param name="side">The side to grow.</param> /// <param name="by">The amount to grow by.</param> /// <returns>The grown <see cref="Rect2"/>.</returns> - public Rect2 GrowSide(Side side, real_t by) + public readonly Rect2 GrowSide(Side side, real_t by) { Rect2 g = this; @@ -242,7 +242,7 @@ namespace Godot /// <returns> /// A <see langword="bool"/> for whether or not the <see cref="Rect2"/> has area. /// </returns> - public bool HasArea() + public readonly bool HasArea() { return _size.x > 0.0f && _size.y > 0.0f; } @@ -255,7 +255,7 @@ namespace Godot /// <returns> /// A <see langword="bool"/> for whether or not the <see cref="Rect2"/> contains <paramref name="point"/>. /// </returns> - public bool HasPoint(Vector2 point) + public readonly bool HasPoint(Vector2 point) { if (point.x < _position.x) return false; @@ -281,7 +281,7 @@ namespace Godot /// <param name="b">The other <see cref="Rect2"/> to check for intersections with.</param> /// <param name="includeBorders">Whether or not to consider borders.</param> /// <returns>A <see langword="bool"/> for whether or not they are intersecting.</returns> - public bool Intersects(Rect2 b, bool includeBorders = false) + public readonly bool Intersects(Rect2 b, bool includeBorders = false) { if (includeBorders) { @@ -330,7 +330,7 @@ namespace Godot /// </summary> /// <param name="b">The other <see cref="Rect2"/>.</param> /// <returns>The merged <see cref="Rect2"/>.</returns> - public Rect2 Merge(Rect2 b) + public readonly Rect2 Merge(Rect2 b) { Rect2 newRect; @@ -426,7 +426,7 @@ namespace Godot /// </summary> /// <param name="obj">The other object to compare.</param> /// <returns>Whether or not the rect and the other object are exactly equal.</returns> - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { return obj is Rect2 other && Equals(other); } @@ -436,7 +436,7 @@ namespace Godot /// </summary> /// <param name="other">The other rect to compare.</param> /// <returns>Whether or not the rects are exactly equal.</returns> - public bool Equals(Rect2 other) + public readonly bool Equals(Rect2 other) { return _position.Equals(other._position) && _size.Equals(other._size); } @@ -447,7 +447,7 @@ namespace Godot /// </summary> /// <param name="other">The other rect to compare.</param> /// <returns>Whether or not the rects are approximately equal.</returns> - public bool IsEqualApprox(Rect2 other) + public readonly bool IsEqualApprox(Rect2 other) { return _position.IsEqualApprox(other._position) && _size.IsEqualApprox(other.Size); } @@ -456,7 +456,7 @@ namespace Godot /// Serves as the hash function for <see cref="Rect2"/>. /// </summary> /// <returns>A hash code for this rect.</returns> - public override int GetHashCode() + public override readonly int GetHashCode() { return _position.GetHashCode() ^ _size.GetHashCode(); } @@ -465,7 +465,7 @@ namespace Godot /// Converts this <see cref="Rect2"/> to a string. /// </summary> /// <returns>A string representation of this rect.</returns> - public override string ToString() + public override readonly string ToString() { return $"{_position}, {_size}"; } @@ -474,7 +474,7 @@ namespace Godot /// Converts this <see cref="Rect2"/> to a string with the given <paramref name="format"/>. /// </summary> /// <returns>A string representation of this rect.</returns> - public string ToString(string format) + public readonly string ToString(string format) { return $"{_position.ToString(format)}, {_size.ToString(format)}"; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs index b2768476cc..faee81a98a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs @@ -20,7 +20,7 @@ namespace Godot /// <value>Directly uses a private field.</value> public Vector2i Position { - get { return _position; } + readonly get { return _position; } set { _position = value; } } @@ -31,7 +31,7 @@ namespace Godot /// <value>Directly uses a private field.</value> public Vector2i Size { - get { return _size; } + readonly get { return _size; } set { _size = value; } } @@ -45,7 +45,7 @@ namespace Godot /// </value> public Vector2i End { - get { return _position + _size; } + readonly get { return _position + _size; } set { _size = value - _position; } } @@ -53,7 +53,7 @@ namespace Godot /// The area of this <see cref="Rect2i"/>. /// </summary> /// <value>Equivalent to <see cref="GetArea()"/>.</value> - public int Area + public readonly int Area { get { return GetArea(); } } @@ -63,7 +63,7 @@ namespace Godot /// the top-left corner is the origin and width and height are positive. /// </summary> /// <returns>The modified <see cref="Rect2i"/>.</returns> - public Rect2i Abs() + public readonly Rect2i Abs() { Vector2i end = End; Vector2i topLeft = new Vector2i(Mathf.Min(_position.x, end.x), Mathf.Min(_position.y, end.y)); @@ -79,7 +79,7 @@ namespace Godot /// The intersection of this <see cref="Rect2i"/> and <paramref name="b"/>, /// or an empty <see cref="Rect2i"/> if they do not intersect. /// </returns> - public Rect2i Intersection(Rect2i b) + public readonly Rect2i Intersection(Rect2i b) { Rect2i newRect = b; @@ -107,7 +107,7 @@ namespace Godot /// <returns> /// A <see langword="bool"/> for whether or not this <see cref="Rect2i"/> encloses <paramref name="b"/>. /// </returns> - public bool Encloses(Rect2i b) + public readonly bool Encloses(Rect2i b) { return b._position.x >= _position.x && b._position.y >= _position.y && b._position.x + b._size.x < _position.x + _size.x && @@ -119,7 +119,7 @@ namespace Godot /// </summary> /// <param name="to">The point to include.</param> /// <returns>The expanded <see cref="Rect2i"/>.</returns> - public Rect2i Expand(Vector2i to) + public readonly Rect2i Expand(Vector2i to) { Rect2i expanded = this; @@ -154,7 +154,7 @@ namespace Godot /// Returns the area of the <see cref="Rect2i"/>. /// </summary> /// <returns>The area.</returns> - public int GetArea() + public readonly int GetArea() { return _size.x * _size.y; } @@ -166,7 +166,7 @@ namespace Godot /// value will be rounded towards <see cref="Position"/>. /// </summary> /// <returns>The center.</returns> - public Vector2i GetCenter() + public readonly Vector2i GetCenter() { return _position + (_size / 2); } @@ -179,7 +179,7 @@ namespace Godot /// <seealso cref="GrowSide(Side, int)"/> /// <param name="by">The amount to grow by.</param> /// <returns>The grown <see cref="Rect2i"/>.</returns> - public Rect2i Grow(int by) + public readonly Rect2i Grow(int by) { Rect2i g = this; @@ -202,7 +202,7 @@ namespace Godot /// <param name="right">The amount to grow by on the right side.</param> /// <param name="bottom">The amount to grow by on the bottom side.</param> /// <returns>The grown <see cref="Rect2i"/>.</returns> - public Rect2i GrowIndividual(int left, int top, int right, int bottom) + public readonly Rect2i GrowIndividual(int left, int top, int right, int bottom) { Rect2i g = this; @@ -223,7 +223,7 @@ namespace Godot /// <param name="side">The side to grow.</param> /// <param name="by">The amount to grow by.</param> /// <returns>The grown <see cref="Rect2i"/>.</returns> - public Rect2i GrowSide(Side side, int by) + public readonly Rect2i GrowSide(Side side, int by) { Rect2i g = this; @@ -244,7 +244,7 @@ namespace Godot /// <returns> /// A <see langword="bool"/> for whether or not the <see cref="Rect2i"/> has area. /// </returns> - public bool HasArea() + public readonly bool HasArea() { return _size.x > 0 && _size.y > 0; } @@ -257,7 +257,7 @@ namespace Godot /// <returns> /// A <see langword="bool"/> for whether or not the <see cref="Rect2i"/> contains <paramref name="point"/>. /// </returns> - public bool HasPoint(Vector2i point) + public readonly bool HasPoint(Vector2i point) { if (point.x < _position.x) return false; @@ -283,7 +283,7 @@ namespace Godot /// <param name="b">The other <see cref="Rect2i"/> to check for intersections with.</param> /// <param name="includeBorders">Whether or not to consider borders.</param> /// <returns>A <see langword="bool"/> for whether or not they are intersecting.</returns> - public bool Intersects(Rect2i b, bool includeBorders = false) + public readonly bool Intersects(Rect2i b, bool includeBorders = false) { if (includeBorders) { @@ -316,7 +316,7 @@ namespace Godot /// </summary> /// <param name="b">The other <see cref="Rect2i"/>.</param> /// <returns>The merged <see cref="Rect2i"/>.</returns> - public Rect2i Merge(Rect2i b) + public readonly Rect2i Merge(Rect2i b) { Rect2i newRect; @@ -426,7 +426,7 @@ namespace Godot /// </summary> /// <param name="obj">The other object to compare.</param> /// <returns>Whether or not the rect and the other object are equal.</returns> - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { return obj is Rect2i other && Equals(other); } @@ -436,7 +436,7 @@ namespace Godot /// </summary> /// <param name="other">The other rect to compare.</param> /// <returns>Whether or not the rects are equal.</returns> - public bool Equals(Rect2i other) + public readonly bool Equals(Rect2i other) { return _position.Equals(other._position) && _size.Equals(other._size); } @@ -445,7 +445,7 @@ namespace Godot /// Serves as the hash function for <see cref="Rect2i"/>. /// </summary> /// <returns>A hash code for this rect.</returns> - public override int GetHashCode() + public override readonly int GetHashCode() { return _position.GetHashCode() ^ _size.GetHashCode(); } @@ -454,7 +454,7 @@ namespace Godot /// Converts this <see cref="Rect2i"/> to a string. /// </summary> /// <returns>A string representation of this rect.</returns> - public override string ToString() + public override readonly string ToString() { return $"{_position}, {_size}"; } @@ -463,7 +463,7 @@ namespace Godot /// Converts this <see cref="Rect2i"/> to a string with the given <paramref name="format"/>. /// </summary> /// <returns>A string representation of this rect.</returns> - public string ToString(string format) + public readonly string ToString(string format) { return $"{_position.ToString(format)}, {_size.ToString(format)}"; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index d77baab24b..d4329d78c1 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Security; +using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using Godot.NativeInterop; @@ -67,30 +69,13 @@ namespace Godot } /// <summary> - /// If the string is a path to a file, return the path to the file without the extension. - /// </summary> - /// <seealso cref="GetExtension(string)"/> - /// <seealso cref="GetBaseDir(string)"/> - /// <seealso cref="GetFile(string)"/> - /// <param name="instance">The path to a file.</param> - /// <returns>The path to the file without the extension.</returns> - public static string GetBaseName(this string instance) - { - int index = instance.LastIndexOf('.'); - - if (index > 0) - return instance.Substring(0, index); - - return instance; - } - - /// <summary> /// Returns <see langword="true"/> if the strings begins /// with the given string <paramref name="text"/>. /// </summary> /// <param name="instance">The string to check.</param> /// <param name="text">The beginning string.</param> /// <returns>If the string begins with the given string.</returns> + [Obsolete("Use string.StartsWith instead.")] public static bool BeginsWith(this string instance, string text) { return instance.StartsWith(text); @@ -144,15 +129,15 @@ namespace Godot } /// <summary> - /// Returns the amount of substrings <paramref name="what"/> in the string. + /// Returns the number of occurrences of substring <paramref name="what"/> in the string. /// </summary> /// <param name="instance">The string where the substring will be searched.</param> /// <param name="what">The substring that will be counted.</param> - /// <param name="caseSensitive">If the search is case sensitive.</param> /// <param name="from">Index to start searching from.</param> /// <param name="to">Index to stop searching at.</param> - /// <returns>Amount of substrings in the string.</returns> - public static int Count(this string instance, string what, bool caseSensitive = true, int from = 0, int to = 0) + /// <param name="caseSensitive">If the search is case sensitive.</param> + /// <returns>Number of occurrences of the substring in the string.</returns> + public static int Count(this string instance, string what, int from = 0, int to = 0, bool caseSensitive = true) { if (what.Length == 0) { @@ -211,6 +196,82 @@ namespace Godot } /// <summary> + /// Returns the number of occurrences of substring <paramref name="what"/> (ignoring case) + /// between <paramref name="from"/> and <paramref name="to"/> positions. If <paramref name="from"/> + /// and <paramref name="to"/> equals 0 the whole string will be used. If only <paramref name="to"/> + /// equals 0 the remained substring will be used. + /// </summary> + /// <param name="instance">The string where the substring will be searched.</param> + /// <param name="what">The substring that will be counted.</param> + /// <param name="from">Index to start searching from.</param> + /// <param name="to">Index to stop searching at.</param> + /// <returns>Number of occurrences of the substring in the string.</returns> + public static int CountN(this string instance, string what, int from = 0, int to = 0) + { + return instance.Count(what, from, to, caseSensitive: false); + } + + /// <summary> + /// Returns a copy of the string with indentation (leading tabs and spaces) removed. + /// See also <see cref="Indent"/> to add indentation. + /// </summary> + /// <param name="instance">The string to remove the indentation from.</param> + /// <returns>The string with the indentation removed.</returns> + public static string Dedent(this string instance) + { + var sb = new StringBuilder(); + string indent = ""; + bool hasIndent = false; + bool hasText = false; + int lineStart = 0; + int indentStop = -1; + + for (int i = 0; i < instance.Length; i++) + { + char c = instance[i]; + if (c == '\n') + { + if (hasText) + { + sb.Append(instance.Substring(indentStop, i - indentStop)); + } + sb.Append('\n'); + hasText = false; + lineStart = i + 1; + indentStop = -1; + } + else if (!hasText) + { + if (c > 32) + { + hasText = true; + if (!hasIndent) + { + hasIndent = true; + indent = instance.Substring(lineStart, i - lineStart); + indentStop = i; + } + } + if (hasIndent && indentStop < 0) + { + int j = i - lineStart; + if (j >= indent.Length || c != indent[j]) + { + indentStop = i; + } + } + } + } + + if (hasText) + { + sb.Append(instance.Substring(indentStop, instance.Length - indentStop)); + } + + return sb.ToString(); + } + + /// <summary> /// Returns a copy of the string with special characters escaped using the C language standard. /// </summary> /// <param name="instance">The string to escape.</param> @@ -229,7 +290,6 @@ namespace Godot sb.Replace("\v", "\\v"); sb.Replace("\'", "\\'"); sb.Replace("\"", "\\\""); - sb.Replace("?", "\\?"); return sb.ToString(); } @@ -253,7 +313,6 @@ namespace Godot sb.Replace("\\v", "\v"); sb.Replace("\\'", "\'"); sb.Replace("\\\"", "\""); - sb.Replace("\\?", "?"); sb.Replace("\\\\", "\\"); return sb.ToString(); @@ -445,29 +504,6 @@ namespace Godot } /// <summary> - /// Returns <see langword="true"/> if the strings ends - /// with the given string <paramref name="text"/>. - /// </summary> - /// <param name="instance">The string to check.</param> - /// <param name="text">The ending string.</param> - /// <returns>If the string ends with the given string.</returns> - public static bool EndsWith(this string instance, string text) - { - return instance.EndsWith(text); - } - - /// <summary> - /// Erase <paramref name="chars"/> characters from the string starting from <paramref name="pos"/>. - /// </summary> - /// <param name="instance">The string to modify.</param> - /// <param name="pos">Starting position from which to erase.</param> - /// <param name="chars">Amount of characters to erase.</param> - public static void Erase(this StringBuilder instance, int pos, int chars) - { - instance.Remove(pos, chars); - } - - /// <summary> /// Returns the extension without the leading period character (<c>.</c>) /// if the string is a valid file name or path. If the string does not contain /// an extension, returns an empty string instead. @@ -491,7 +527,7 @@ namespace Godot /// <returns>The extension of the file or an empty string.</returns> public static string GetExtension(this string instance) { - int pos = instance.FindLast("."); + int pos = instance.RFind("."); if (pos < 0) return instance; @@ -500,12 +536,16 @@ namespace Godot } /// <summary> - /// Find the first occurrence of a substring. Optionally, the search starting position can be passed. + /// Returns the index of the first occurrence of the specified string in this instance, + /// or <c>-1</c>. Optionally, the starting search index can be specified, continuing + /// to the end of the string. + /// Note: If you just want to know whether a string contains a substring, use the + /// <see cref="string.Contains(string)"/> method. /// </summary> /// <seealso cref="Find(string, char, int, bool)"/> - /// <seealso cref="FindLast(string, string, bool)"/> - /// <seealso cref="FindLast(string, string, int, bool)"/> /// <seealso cref="FindN(string, string, int)"/> + /// <seealso cref="RFind(string, string, int, bool)"/> + /// <seealso cref="RFindN(string, string, int)"/> /// <param name="instance">The string that will be searched.</param> /// <param name="what">The substring to find.</param> /// <param name="from">The search starting position.</param> @@ -521,9 +561,9 @@ namespace Godot /// Find the first occurrence of a char. Optionally, the search starting position can be passed. /// </summary> /// <seealso cref="Find(string, string, int, bool)"/> - /// <seealso cref="FindLast(string, string, bool)"/> - /// <seealso cref="FindLast(string, string, int, bool)"/> /// <seealso cref="FindN(string, string, int)"/> + /// <seealso cref="RFind(string, string, int, bool)"/> + /// <seealso cref="RFindN(string, string, int)"/> /// <param name="instance">The string that will be searched.</param> /// <param name="what">The substring to find.</param> /// <param name="from">The search starting position.</param> @@ -531,50 +571,21 @@ namespace Godot /// <returns>The first instance of the char, or -1 if not found.</returns> public static int Find(this string instance, char what, int from = 0, bool caseSensitive = true) { - // TODO: Could be more efficient if we get a char version of `IndexOf`. - // See https://github.com/dotnet/runtime/issues/44116 - return instance.IndexOf(what.ToString(), from, - caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); - } + if (caseSensitive) + return instance.IndexOf(what, from); - /// <summary>Find the last occurrence of a substring.</summary> - /// <seealso cref="Find(string, string, int, bool)"/> - /// <seealso cref="Find(string, char, int, bool)"/> - /// <seealso cref="FindLast(string, string, int, bool)"/> - /// <seealso cref="FindN(string, string, int)"/> - /// <param name="instance">The string that will be searched.</param> - /// <param name="what">The substring to find.</param> - /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param> - /// <returns>The starting position of the substring, or -1 if not found.</returns> - public static int FindLast(this string instance, string what, bool caseSensitive = true) - { - return instance.FindLast(what, instance.Length - 1, caseSensitive); - } - - /// <summary>Find the last occurrence of a substring specifying the search starting position.</summary> - /// <seealso cref="Find(string, string, int, bool)"/> - /// <seealso cref="Find(string, char, int, bool)"/> - /// <seealso cref="FindLast(string, string, bool)"/> - /// <seealso cref="FindN(string, string, int)"/> - /// <param name="instance">The string that will be searched.</param> - /// <param name="what">The substring to find.</param> - /// <param name="from">The search starting position.</param> - /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param> - /// <returns>The starting position of the substring, or -1 if not found.</returns> - public static int FindLast(this string instance, string what, int from, bool caseSensitive = true) - { - return instance.LastIndexOf(what, from, - caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); + return CultureInfo.InvariantCulture.CompareInfo.IndexOf(instance, what, from, CompareOptions.OrdinalIgnoreCase); } /// <summary> - /// Find the first occurrence of a substring but search as case-insensitive. - /// Optionally, the search starting position can be passed. + /// Returns the index of the first case-insensitive occurrence of the specified string in this instance, + /// or <c>-1</c>. Optionally, the starting search index can be specified, continuing + /// to the end of the string. /// </summary> /// <seealso cref="Find(string, string, int, bool)"/> /// <seealso cref="Find(string, char, int, bool)"/> - /// <seealso cref="FindLast(string, string, bool)"/> - /// <seealso cref="FindLast(string, string, int, bool)"/> + /// <seealso cref="RFind(string, string, int, bool)"/> + /// <seealso cref="RFindN(string, string, int)"/> /// <param name="instance">The string that will be searched.</param> /// <param name="what">The substring to find.</param> /// <param name="from">The search starting position.</param> @@ -618,7 +629,7 @@ namespace Godot } } - int sep = Mathf.Max(rs.FindLast("/"), rs.FindLast("\\")); + int sep = Mathf.Max(rs.RFind("/"), rs.RFind("\\")); if (sep == -1) return directory; @@ -627,6 +638,24 @@ namespace Godot } /// <summary> + /// If the string is a path to a file, return the path to the file without the extension. + /// </summary> + /// <seealso cref="GetExtension(string)"/> + /// <seealso cref="GetBaseDir(string)"/> + /// <seealso cref="GetFile(string)"/> + /// <param name="instance">The path to a file.</param> + /// <returns>The path to the file without the extension.</returns> + public static string GetBaseName(this string instance) + { + int index = instance.RFind("."); + + if (index > 0) + return instance.Substring(0, index); + + return instance; + } + + /// <summary> /// If the string is a path to a file, return the file and ignore the base directory. /// </summary> /// <seealso cref="GetBaseName(string)"/> @@ -636,7 +665,7 @@ namespace Godot /// <returns>The file name.</returns> public static string GetFile(this string instance) { - int sep = Mathf.Max(instance.FindLast("/"), instance.FindLast("\\")); + int sep = Mathf.Max(instance.RFind("/"), instance.RFind("\\")); if (sep == -1) return instance; @@ -645,8 +674,8 @@ namespace Godot } /// <summary> - /// Converts the given byte array of ASCII encoded text to a string. - /// Faster alternative to <see cref="GetStringFromUTF8"/> if the + /// Converts ASCII encoded array to string. + /// Fast alternative to <see cref="GetStringFromUTF8"/> if the /// content is ASCII-only. Unlike the UTF-8 function this function /// maps every byte to a character in the array. Multibyte sequences /// will not be interpreted correctly. For parsing user input always @@ -660,13 +689,35 @@ namespace Godot } /// <summary> - /// Converts the given byte array of UTF-8 encoded text to a string. + /// Converts UTF-16 encoded array to string using the little endian byte order. + /// </summary> + /// <param name="bytes">A byte array of UTF-16 characters.</param> + /// <returns>A string created from the bytes.</returns> + public static string GetStringFromUTF16(this byte[] bytes) + { + return Encoding.Unicode.GetString(bytes); + } + + /// <summary> + /// Converts UTF-32 encoded array to string using the little endian byte order. + /// </summary> + /// <param name="bytes">A byte array of UTF-32 characters.</param> + /// <returns>A string created from the bytes.</returns> + public static string GetStringFromUTF32(this byte[] bytes) + { + return Encoding.UTF32.GetString(bytes); + } + + /// <summary> + /// Converts UTF-8 encoded array to string. /// Slower than <see cref="GetStringFromASCII"/> but supports UTF-8 /// encoded data. Use this function if you are unsure about the /// source of the data. For user input this function /// should always be preferred. /// </summary> - /// <param name="bytes">A byte array of UTF-8 characters (a character may take up multiple bytes).</param> + /// <param name="bytes"> + /// A byte array of UTF-8 characters (a character may take up multiple bytes). + /// </param> /// <returns>A string created from the bytes.</returns> public static string GetStringFromUTF8(this byte[] bytes) { @@ -768,18 +819,44 @@ namespace Godot } /// <summary> - /// Inserts a substring at a given position. + /// Returns a copy of the string with lines indented with <paramref name="prefix"/>. + /// For example, the string can be indented with two tabs using <c>"\t\t"</c>, + /// or four spaces using <c>" "</c>. The prefix can be any string so it can + /// also be used to comment out strings with e.g. <c>"// </c>. + /// See also <see cref="Dedent"/> to remove indentation. + /// Note: Empty lines are kept empty. /// </summary> - /// <param name="instance">The string to modify.</param> - /// <param name="pos">Position at which to insert the substring.</param> - /// <param name="what">Substring to insert.</param> - /// <returns> - /// The string with <paramref name="what"/> inserted at the given - /// position <paramref name="pos"/>. - /// </returns> - public static string Insert(this string instance, int pos, string what) + /// <param name="instance">The string to add indentation to.</param> + /// <param name="prefix">The string to use as indentation.</param> + /// <returns>The string with indentation added.</returns> + public static string Indent(this string instance, string prefix) { - return instance.Insert(pos, what); + var sb = new StringBuilder(); + int lineStart = 0; + + for (int i = 0; i < instance.Length; i++) + { + char c = instance[i]; + if (c == '\n') + { + if (i == lineStart) + { + sb.Append(c); // Leave empty lines empty. + } + else + { + sb.Append(prefix); + sb.Append(instance.Substring(lineStart, i - lineStart + 1)); + } + lineStart = i + 1; + } + } + if (lineStart != instance.Length) + { + sb.Append(prefix); + sb.Append(instance.Substring(lineStart)); + } + return sb.ToString(); } /// <summary> @@ -875,19 +952,94 @@ namespace Godot return instance.IsSubsequenceOf(text, caseSensitive: false); } + private static readonly char[] _invalidFileNameCharacters = { ':', '/', '\\', '?', '*', '"', '|', '%', '<', '>' }; + + /// <summary> + /// Returns <see langword="true"/> if this string is free from characters that + /// aren't allowed in file names. + /// </summary> + /// <param name="instance">The string to check.</param> + /// <returns>If the string contains a valid file name.</returns> + public static bool IsValidFileName(this string instance) + { + var stripped = instance.Trim(); + if (instance != stripped) + return false; + + if (string.IsNullOrEmpty(stripped)) + return false; + + return instance.IndexOfAny(_invalidFileNameCharacters) == -1; + } + /// <summary> - /// Check whether the string contains a valid <see langword="float"/>. + /// Returns <see langword="true"/> if this string contains a valid <see langword="float"/>. + /// This is inclusive of integers, and also supports exponents. /// </summary> + /// <example> + /// <code> + /// GD.Print("1.7".IsValidFloat()) // Prints "True" + /// GD.Print("24".IsValidFloat()) // Prints "True" + /// GD.Print("7e3".IsValidFloat()) // Prints "True" + /// GD.Print("Hello".IsValidFloat()) // Prints "False" + /// </code> + /// </example> /// <param name="instance">The string to check.</param> /// <returns>If the string contains a valid floating point number.</returns> public static bool IsValidFloat(this string instance) { - float f; - return float.TryParse(instance, out f); + return float.TryParse(instance, out _); + } + + /// <summary> + /// Returns <see langword="true"/> if this string contains a valid hexadecimal number. + /// If <paramref name="withPrefix"/> is <see langword="true"/>, then a validity of the + /// hexadecimal number is determined by <c>0x</c> prefix, for instance: <c>0xDEADC0DE</c>. + /// </summary> + /// <param name="instance">The string to check.</param> + /// <param name="withPrefix">If the string must contain the <c>0x</c> prefix to be valid.</param> + /// <returns>If the string contains a valid hexadecimal number.</returns> + public static bool IsValidHexNumber(this string instance, bool withPrefix = false) + { + if (string.IsNullOrEmpty(instance)) + return false; + + int from = 0; + if (instance.Length != 1 && instance[0] == '+' || instance[0] == '-') + { + from++; + } + + if (withPrefix) + { + if (instance.Length < 3) + return false; + if (instance[from] != '0' || instance[from + 1] != 'x') + return false; + from += 2; + } + + for (int i = from; i < instance.Length; i++) + { + char c = instance[i]; + if (IsHexDigit(c)) + continue; + + return false; + } + + return true; + + static bool IsHexDigit(char c) + { + return char.IsDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); + } } /// <summary> - /// Check whether the string contains a valid color in HTML notation. + /// Returns <see langword="true"/> if this string contains a valid color in hexadecimal + /// HTML notation. Other HTML notations such as named colors or <c>hsl()</c> aren't + /// considered valid by this method and will return <see langword="false"/>. /// </summary> /// <param name="instance">The string to check.</param> /// <returns>If the string contains a valid HTML color.</returns> @@ -897,10 +1049,17 @@ namespace Godot } /// <summary> - /// Check whether the string is a valid identifier. As is common in - /// programming languages, a valid identifier may contain only letters, - /// digits and underscores (_) and the first character may not be a digit. + /// Returns <see langword="true"/> if this string is a valid identifier. + /// A valid identifier may contain only letters, digits and underscores (<c>_</c>) + /// and the first character may not be a digit. /// </summary> + /// <example> + /// <code> + /// GD.Print("good_ident_1".IsValidIdentifier()) // Prints "True" + /// GD.Print("1st_bad_ident".IsValidIdentifier()) // Prints "False" + /// GD.Print("bad_ident_#2".IsValidIdentifier()) // Prints "False" + /// </code> + /// </example> /// <param name="instance">The string to check.</param> /// <returns>If the string contains a valid identifier.</returns> public static bool IsValidIdentifier(this string instance) @@ -928,38 +1087,73 @@ namespace Godot } /// <summary> - /// Check whether the string contains a valid integer. + /// Returns <see langword="true"/> if this string contains a valid <see langword="int"/>. /// </summary> + /// <example> + /// <code> + /// GD.Print("7".IsValidInt()) // Prints "True" + /// GD.Print("14.6".IsValidInt()) // Prints "False" + /// GD.Print("L".IsValidInt()) // Prints "False" + /// GD.Print("+3".IsValidInt()) // Prints "True" + /// GD.Print("-12".IsValidInt()) // Prints "True" + /// </code> + /// </example> /// <param name="instance">The string to check.</param> /// <returns>If the string contains a valid integer.</returns> - public static bool IsValidInteger(this string instance) + public static bool IsValidInt(this string instance) { - int f; - return int.TryParse(instance, out f); + return int.TryParse(instance, out _); } /// <summary> - /// Check whether the string contains a valid IP address. + /// Returns <see langword="true"/> if this string contains only a well-formatted + /// IPv4 or IPv6 address. This method considers reserved IP addresses such as + /// <c>0.0.0.0</c> as valid. /// </summary> /// <param name="instance">The string to check.</param> /// <returns>If the string contains a valid IP address.</returns> public static bool IsValidIPAddress(this string instance) { - // TODO: Support IPv6 addresses - string[] ip = instance.Split("."); + if (instance.Contains(':')) + { + string[] ip = instance.Split(':'); - if (ip.Length != 4) - return false; + for (int i = 0; i < ip.Length; i++) + { + string n = ip[i]; + if (n.Length == 0) + continue; + + if (n.IsValidHexNumber(withPrefix: false)) + { + long nint = n.HexToInt(); + if (nint < 0 || nint > 0xffff) + return false; - for (int i = 0; i < ip.Length; i++) + continue; + } + + if (!n.IsValidIPAddress()) + return false; + } + } + else { - string n = ip[i]; - if (!n.IsValidInteger()) - return false; + string[] ip = instance.Split('.'); - int val = n.ToInt(); - if (val < 0 || val > 255) + if (ip.Length != 4) return false; + + for (int i = 0; i < ip.Length; i++) + { + string n = ip[i]; + if (!n.IsValidInt()) + return false; + + int val = n.ToInt(); + if (val < 0 || val > 255) + return false; + } } return true; @@ -1005,41 +1199,20 @@ namespace Godot } /// <summary> - /// Returns the length of the string in characters. - /// </summary> - /// <param name="instance">The string to check.</param> - /// <returns>The length of the string.</returns> - public static int Length(this string instance) - { - return instance.Length; - } - - /// <summary> /// Returns a copy of the string with characters removed from the left. + /// The <paramref name="chars"/> argument is a string specifying the set of characters + /// to be removed. + /// Note: The <paramref name="chars"/> is not a prefix. See <see cref="TrimPrefix"/> + /// method that will remove a single prefix string rather than a set of characters. /// </summary> /// <seealso cref="RStrip(string, string)"/> /// <param name="instance">The string to remove characters from.</param> /// <param name="chars">The characters to be removed.</param> /// <returns>A copy of the string with characters removed from the left.</returns> + [Obsolete("Use string.TrimStart instead.")] public static string LStrip(this string instance, string chars) { - int len = instance.Length; - int beg; - - for (beg = 0; beg < len; beg++) - { - if (chars.Find(instance[beg]) == -1) - { - break; - } - } - - if (beg == 0) - { - return instance; - } - - return instance.Substr(beg, len - beg); + return instance.TrimStart(chars.ToCharArray()); } /// <summary> @@ -1119,10 +1292,9 @@ namespace Godot /// <returns>The MD5 hash of the string.</returns> public static byte[] MD5Buffer(this string instance) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - NativeFuncs.godotsharp_string_md5_buffer(instanceStr, out var md5Buffer); - using (md5Buffer) - return Marshaling.ConvertNativePackedByteArrayToSystemArray(md5Buffer); +#pragma warning disable CA5351 // Do Not Use Broken Cryptographic Algorithms + return MD5.HashData(Encoding.UTF8.GetBytes(instance)); +#pragma warning restore CA5351 } /// <summary> @@ -1133,10 +1305,7 @@ namespace Godot /// <returns>The MD5 hash of the string.</returns> public static string MD5Text(this string instance) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - NativeFuncs.godotsharp_string_md5_text(instanceStr, out var md5Text); - using (md5Text) - return Marshaling.ConvertStringToManaged(md5Text); + return instance.MD5Buffer().HexEncode(); } /// <summary> @@ -1153,17 +1322,6 @@ namespace Godot } /// <summary> - /// Returns the character code at position <paramref name="at"/>. - /// </summary> - /// <param name="instance">The string to check.</param> - /// <param name="at">The position int the string for the character to check.</param> - /// <returns>The character code.</returns> - public static int OrdAt(this string instance, int at) - { - return instance[at]; - } - - /// <summary> /// Format a number to have an exact number of <paramref name="digits"/> /// after the decimal point. /// </summary> @@ -1284,34 +1442,47 @@ namespace Godot } /// <summary> - /// Perform a search for a substring, but start from the end of the string instead of the beginning. + /// Returns the index of the last occurrence of the specified string in this instance, + /// or <c>-1</c>. Optionally, the starting search index can be specified, continuing to + /// the beginning of the string. /// </summary> + /// <seealso cref="Find(string, string, int, bool)"/> + /// <seealso cref="Find(string, char, int, bool)"/> + /// <seealso cref="FindN(string, string, int)"/> /// <seealso cref="RFindN(string, string, int)"/> /// <param name="instance">The string that will be searched.</param> /// <param name="what">The substring to search in the string.</param> /// <param name="from">The position at which to start searching.</param> + /// <param name="caseSensitive">If <see langword="true"/>, the search is case sensitive.</param> /// <returns>The position at which the substring was found, or -1 if not found.</returns> - public static int RFind(this string instance, string what, int from = -1) + public static int RFind(this string instance, string what, int from = -1, bool caseSensitive = true) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - using godot_string whatStr = Marshaling.ConvertStringToNative(instance); - return NativeFuncs.godotsharp_string_rfind(instanceStr, whatStr, from); + if (from == -1) + from = instance.Length - 1; + + return instance.LastIndexOf(what, from, + caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); } /// <summary> - /// Perform a search for a substring, but start from the end of the string instead of the beginning. - /// Also search case-insensitive. + /// Returns the index of the last case-insensitive occurrence of the specified string in this instance, + /// or <c>-1</c>. Optionally, the starting search index can be specified, continuing to + /// the beginning of the string. /// </summary> - /// <seealso cref="RFind(string, string, int)"/> + /// <seealso cref="Find(string, string, int, bool)"/> + /// <seealso cref="Find(string, char, int, bool)"/> + /// <seealso cref="FindN(string, string, int)"/> + /// <seealso cref="RFind(string, string, int, bool)"/> /// <param name="instance">The string that will be searched.</param> /// <param name="what">The substring to search in the string.</param> /// <param name="from">The position at which to start searching.</param> /// <returns>The position at which the substring was found, or -1 if not found.</returns> public static int RFindN(this string instance, string what, int from = -1) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - using godot_string whatStr = Marshaling.ConvertStringToNative(instance); - return NativeFuncs.godotsharp_string_rfindn(instanceStr, whatStr, from); + if (from == -1) + from = instance.Length - 1; + + return instance.LastIndexOf(what, from, StringComparison.OrdinalIgnoreCase); } /// <summary> @@ -1334,30 +1505,43 @@ namespace Godot /// <summary> /// Returns a copy of the string with characters removed from the right. + /// The <paramref name="chars"/> argument is a string specifying the set of characters + /// to be removed. + /// Note: The <paramref name="chars"/> is not a suffix. See <see cref="TrimSuffix"/> + /// method that will remove a single suffix string rather than a set of characters. /// </summary> /// <seealso cref="LStrip(string, string)"/> /// <param name="instance">The string to remove characters from.</param> /// <param name="chars">The characters to be removed.</param> /// <returns>A copy of the string with characters removed from the right.</returns> + [Obsolete("Use string.TrimEnd instead.")] public static string RStrip(this string instance, string chars) { - int len = instance.Length; - int end; - - for (end = len - 1; end >= 0; end--) - { - if (chars.Find(instance[end]) == -1) - { - break; - } - } + return instance.TrimEnd(chars.ToCharArray()); + } - if (end == len - 1) - { - return instance; - } + /// <summary> + /// Returns the SHA-1 hash of the string as an array of bytes. + /// </summary> + /// <seealso cref="SHA1Text(string)"/> + /// <param name="instance">The string to hash.</param> + /// <returns>The SHA-1 hash of the string.</returns> + public static byte[] SHA1Buffer(this string instance) + { +#pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms + return SHA1.HashData(Encoding.UTF8.GetBytes(instance)); +#pragma warning restore CA5350 + } - return instance.Substr(0, end + 1); + /// <summary> + /// Returns the SHA-1 hash of the string as a string. + /// </summary> + /// <seealso cref="SHA1Buffer(string)"/> + /// <param name="instance">The string to hash.</param> + /// <returns>The SHA-1 hash of the string.</returns> + public static string SHA1Text(this string instance) + { + return instance.SHA1Buffer().HexEncode(); } /// <summary> @@ -1368,10 +1552,7 @@ namespace Godot /// <returns>The SHA-256 hash of the string.</returns> public static byte[] SHA256Buffer(this string instance) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - NativeFuncs.godotsharp_string_sha256_buffer(instanceStr, out var sha256Buffer); - using (sha256Buffer) - return Marshaling.ConvertNativePackedByteArrayToSystemArray(sha256Buffer); + return SHA256.HashData(Encoding.UTF8.GetBytes(instance)); } /// <summary> @@ -1382,10 +1563,7 @@ namespace Godot /// <returns>The SHA-256 hash of the string.</returns> public static string SHA256Text(this string instance) { - using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); - NativeFuncs.godotsharp_string_sha256_text(instanceStr, out var sha256Text); - using (sha256Text) - return Marshaling.ConvertStringToManaged(sha256Text); + return instance.SHA256Buffer().HexEncode(); } /// <summary> @@ -1457,7 +1635,7 @@ namespace Godot /// <returns>The array of strings split from the string.</returns> public static string[] Split(this string instance, string divisor, bool allowEmpty = true) { - return instance.Split(new[] { divisor }, + return instance.Split(divisor, allowEmpty ? StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries); } @@ -1505,8 +1683,10 @@ namespace Godot }; /// <summary> - /// Returns a copy of the string stripped of any non-printable character at the beginning and the end. - /// The optional arguments are used to toggle stripping on the left and right edges respectively. + /// Returns a copy of the string stripped of any non-printable character + /// (including tabulations, spaces and line breaks) at the beginning and the end. + /// The optional arguments are used to toggle stripping on the left and right + /// edges respectively. /// </summary> /// <param name="instance">The string to strip.</param> /// <param name="left">If the left side should be stripped.</param> @@ -1524,6 +1704,30 @@ namespace Godot return instance.TrimEnd(_nonPrintable); } + + /// <summary> + /// Returns a copy of the string stripped of any escape character. + /// These include all non-printable control characters of the first page + /// of the ASCII table (< 32), such as tabulation (<c>\t</c>) and + /// newline (<c>\n</c> and <c>\r</c>) characters, but not spaces. + /// </summary> + /// <param name="instance">The string to strip.</param> + /// <returns>The string stripped of any escape characters.</returns> + public static string StripEscapes(this string instance) + { + var sb = new StringBuilder(); + for (int i = 0; i < instance.Length; i++) + { + // Escape characters on first page of the ASCII table, before 32 (Space). + if (instance[i] < 32) + continue; + + sb.Append(instance[i]); + } + + return sb.ToString(); + } + /// <summary> /// Returns part of the string from the position <paramref name="from"/>, with length <paramref name="len"/>. /// </summary> @@ -1541,13 +1745,15 @@ namespace Godot /// <summary> /// Converts the String (which is a character array) to PackedByteArray (which is an array of bytes). - /// The conversion is speeded up in comparison to <see cref="ToUTF8(string)"/> with the assumption - /// that all the characters the String contains are only ASCII characters. + /// The conversion is faster compared to <see cref="ToUTF8Buffer(string)"/>, + /// as this method assumes that all the characters in the String are ASCII characters. /// </summary> - /// <seealso cref="ToUTF8(string)"/> + /// <seealso cref="ToUTF8Buffer(string)"/> + /// <seealso cref="ToUTF16Buffer(string)"/> + /// <seealso cref="ToUTF32Buffer(string)"/> /// <param name="instance">The string to convert.</param> /// <returns>The string as ASCII encoded bytes.</returns> - public static byte[] ToAscii(this string instance) + public static byte[] ToASCIIBuffer(this string instance) { return Encoding.ASCII.GetBytes(instance); } @@ -1575,41 +1781,76 @@ namespace Godot } /// <summary> - /// Returns the string converted to lowercase. + /// Converts the string (which is an array of characters) to an UTF-16 encoded array of bytes. /// </summary> - /// <seealso cref="ToUpper(string)"/> + /// <seealso cref="ToASCIIBuffer(string)"/> + /// <seealso cref="ToUTF32Buffer(string)"/> + /// <seealso cref="ToUTF8Buffer(string)"/> /// <param name="instance">The string to convert.</param> - /// <returns>The string converted to lowercase.</returns> - public static string ToLower(this string instance) + /// <returns>The string as UTF-16 encoded bytes.</returns> + public static byte[] ToUTF16Buffer(this string instance) { - return instance.ToLower(); + return Encoding.Unicode.GetBytes(instance); } /// <summary> - /// Returns the string converted to uppercase. + /// Converts the string (which is an array of characters) to an UTF-32 encoded array of bytes. /// </summary> - /// <seealso cref="ToLower(string)"/> + /// <seealso cref="ToASCIIBuffer(string)"/> + /// <seealso cref="ToUTF16Buffer(string)"/> + /// <seealso cref="ToUTF8Buffer(string)"/> /// <param name="instance">The string to convert.</param> - /// <returns>The string converted to uppercase.</returns> - public static string ToUpper(this string instance) + /// <returns>The string as UTF-32 encoded bytes.</returns> + public static byte[] ToUTF32Buffer(this string instance) { - return instance.ToUpper(); + return Encoding.UTF32.GetBytes(instance); } /// <summary> - /// Converts the String (which is an array of characters) to PackedByteArray (which is an array of bytes). - /// The conversion is a bit slower than <see cref="ToAscii(string)"/>, but supports all UTF-8 characters. - /// Therefore, you should prefer this function over <see cref="ToAscii(string)"/>. + /// Converts the string (which is an array of characters) to an UTF-8 encoded array of bytes. + /// The conversion is a bit slower than <see cref="ToASCIIBuffer(string)"/>, + /// but supports all UTF-8 characters. Therefore, you should prefer this function + /// over <see cref="ToASCIIBuffer(string)"/>. /// </summary> - /// <seealso cref="ToAscii(string)"/> + /// <seealso cref="ToASCIIBuffer(string)"/> + /// <seealso cref="ToUTF16Buffer(string)"/> + /// <seealso cref="ToUTF32Buffer(string)"/> /// <param name="instance">The string to convert.</param> /// <returns>The string as UTF-8 encoded bytes.</returns> - public static byte[] ToUTF8(this string instance) + public static byte[] ToUTF8Buffer(this string instance) { return Encoding.UTF8.GetBytes(instance); } /// <summary> + /// Removes a given string from the start if it starts with it or leaves the string unchanged. + /// </summary> + /// <param name="instance">The string to remove the prefix from.</param> + /// <param name="prefix">The string to remove from the start.</param> + /// <returns>A copy of the string with the prefix string removed from the start.</returns> + public static string TrimPrefix(this string instance, string prefix) + { + if (instance.StartsWith(prefix)) + return instance.Substring(prefix.Length); + + return instance; + } + + /// <summary> + /// Removes a given string from the end if it ends with it or leaves the string unchanged. + /// </summary> + /// <param name="instance">The string to remove the suffix from.</param> + /// <param name="suffix">The string to remove from the end.</param> + /// <returns>A copy of the string with the suffix string removed from the end.</returns> + public static string TrimSuffix(this string instance, string suffix) + { + if (instance.EndsWith(suffix)) + return instance.Substring(0, instance.Length - suffix.Length); + + return instance; + } + + /// <summary> /// Decodes a string in URL encoded format. This is meant to /// decode parameters in a URL when receiving an HTTP request. /// This mostly wraps around <see cref="Uri.UnescapeDataString"/>, @@ -1636,6 +1877,25 @@ namespace Godot return Uri.EscapeDataString(instance); } + private const string _uniqueNodePrefix = "%"; + private static readonly string[] _invalidNodeNameCharacters = { ".", ":", "@", "/", "\"", _uniqueNodePrefix }; + + /// <summary> + /// Removes any characters from the string that are prohibited in + /// <see cref="Node"/> names (<c>.</c> <c>:</c> <c>@</c> <c>/</c> <c>"</c>). + /// </summary> + /// <param name="instance">The string to sanitize.</param> + /// <returns>The string sanitized as a valid node name.</returns> + public static string ValidateNodeName(this string instance) + { + string name = instance.Replace(_invalidNodeNameCharacters[0], ""); + for (int i = 1; i < _invalidNodeNameCharacters.Length; i++) + { + name = name.Replace(_invalidNodeNameCharacters[i], ""); + } + return name; + } + /// <summary> /// Returns a copy of the string with special characters escaped using the XML standard. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs index 894667db76..756f71e5b2 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs @@ -37,7 +37,7 @@ namespace Godot /// <value>Getting is equivalent to calling <see cref="Mathf.Atan2(real_t, real_t)"/> with the values of <see cref="x"/>.</value> public real_t Rotation { - get + readonly get { return Mathf.Atan2(x.y, x.x); } @@ -57,7 +57,7 @@ namespace Godot /// <value>Equivalent to the lengths of each column vector, but Y is negative if the determinant is negative.</value> public Vector2 Scale { - get + readonly get { real_t detSign = Mathf.Sign(BasisDeterminant()); return new Vector2(x.Length(), detSign * y.Length()); @@ -80,7 +80,7 @@ namespace Godot /// </exception> public Vector2 this[int column] { - get + readonly get { switch (column) { @@ -121,7 +121,7 @@ namespace Godot /// <param name="row">Which row, the matrix vertical position.</param> public real_t this[int column, int row] { - get + readonly get { return this[column][row]; } @@ -139,7 +139,7 @@ namespace Godot /// </summary> /// <seealso cref="Inverse"/> /// <returns>The inverse transformation matrix.</returns> - public Transform2D AffineInverse() + public readonly Transform2D AffineInverse() { real_t det = BasisDeterminant(); @@ -148,9 +148,8 @@ namespace Godot Transform2D inv = this; - real_t temp = inv[0, 0]; - inv[0, 0] = inv[1, 1]; - inv[1, 1] = temp; + inv[0, 0] = this[1, 1]; + inv[1, 1] = this[0, 0]; real_t detInv = 1.0f / det; @@ -171,7 +170,7 @@ namespace Godot /// and is usually considered invalid. /// </summary> /// <returns>The determinant of the basis matrix.</returns> - private real_t BasisDeterminant() + private readonly real_t BasisDeterminant() { return (x.x * y.y) - (x.y * y.x); } @@ -183,7 +182,7 @@ namespace Godot /// <seealso cref="BasisXformInv(Vector2)"/> /// <param name="v">A vector to transform.</param> /// <returns>The transformed vector.</returns> - public Vector2 BasisXform(Vector2 v) + public readonly Vector2 BasisXform(Vector2 v) { return new Vector2(Tdotx(v), Tdoty(v)); } @@ -198,7 +197,7 @@ namespace Godot /// <seealso cref="BasisXform(Vector2)"/> /// <param name="v">A vector to inversely transform.</param> /// <returns>The inversely transformed vector.</returns> - public Vector2 BasisXformInv(Vector2 v) + public readonly Vector2 BasisXformInv(Vector2 v) { return new Vector2(x.Dot(v), y.Dot(v)); } @@ -209,7 +208,7 @@ namespace Godot /// <param name="transform">The other transform.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The interpolated transform.</returns> - public Transform2D InterpolateWith(Transform2D transform, real_t weight) + public readonly Transform2D InterpolateWith(Transform2D transform, real_t weight) { real_t r1 = Rotation; real_t r2 = transform.Rotation; @@ -258,14 +257,13 @@ namespace Godot /// (no scaling, use <see cref="AffineInverse"/> for transforms with scaling). /// </summary> /// <returns>The inverse matrix.</returns> - public Transform2D Inverse() + public readonly Transform2D Inverse() { Transform2D inv = this; // Swap - real_t temp = inv.x.y; - inv.x.y = inv.y.x; - inv.y.x = temp; + inv.x.y = y.x; + inv.y.x = x.y; inv.origin = inv.BasisXform(-inv.origin); @@ -277,7 +275,7 @@ namespace Godot /// and normalized axis vectors (scale of 1 or -1). /// </summary> /// <returns>The orthonormalized transform.</returns> - public Transform2D Orthonormalized() + public readonly Transform2D Orthonormalized() { Transform2D on = this; @@ -301,7 +299,7 @@ namespace Godot /// </summary> /// <param name="angle">The angle to rotate, in radians.</param> /// <returns>The rotated transformation matrix.</returns> - public Transform2D Rotated(real_t angle) + public readonly Transform2D Rotated(real_t angle) { return this * new Transform2D(angle, new Vector2()); } @@ -313,7 +311,7 @@ namespace Godot /// </summary> /// <param name="angle">The angle to rotate, in radians.</param> /// <returns>The rotated transformation matrix.</returns> - public Transform2D RotatedLocal(real_t angle) + public readonly Transform2D RotatedLocal(real_t angle) { return new Transform2D(angle, new Vector2()) * this; } @@ -325,7 +323,7 @@ namespace Godot /// </summary> /// <param name="scale">The scale to introduce.</param> /// <returns>The scaled transformation matrix.</returns> - public Transform2D Scaled(Vector2 scale) + public readonly Transform2D Scaled(Vector2 scale) { Transform2D copy = this; copy.x *= scale; @@ -341,7 +339,7 @@ namespace Godot /// </summary> /// <param name="scale">The scale to introduce.</param> /// <returns>The scaled transformation matrix.</returns> - public Transform2D ScaledLocal(Vector2 scale) + public readonly Transform2D ScaledLocal(Vector2 scale) { Transform2D copy = this; copy.x *= scale; @@ -349,12 +347,12 @@ namespace Godot return copy; } - private real_t Tdotx(Vector2 with) + private readonly real_t Tdotx(Vector2 with) { return (this[0, 0] * with[0]) + (this[1, 0] * with[1]); } - private real_t Tdoty(Vector2 with) + private readonly real_t Tdoty(Vector2 with) { return (this[0, 1] * with[0]) + (this[1, 1] * with[1]); } @@ -366,7 +364,7 @@ namespace Godot /// </summary> /// <param name="offset">The offset to translate by.</param> /// <returns>The translated matrix.</returns> - public Transform2D Translated(Vector2 offset) + public readonly Transform2D Translated(Vector2 offset) { Transform2D copy = this; copy.origin += offset; @@ -380,7 +378,7 @@ namespace Godot /// </summary> /// <param name="offset">The offset to translate by.</param> /// <returns>The translated matrix.</returns> - public Transform2D TranslatedLocal(Vector2 offset) + public readonly Transform2D TranslatedLocal(Vector2 offset) { Transform2D copy = this; copy.origin += copy.BasisXform(offset); @@ -603,7 +601,7 @@ namespace Godot /// </summary> /// <param name="obj">The object to compare with.</param> /// <returns>Whether or not the transform and the object are exactly equal.</returns> - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { return obj is Transform2D other && Equals(other); } @@ -615,7 +613,7 @@ namespace Godot /// </summary> /// <param name="other">The other transform to compare.</param> /// <returns>Whether or not the matrices are exactly equal.</returns> - public bool Equals(Transform2D other) + public readonly bool Equals(Transform2D other) { return x.Equals(other.x) && y.Equals(other.y) && origin.Equals(other.origin); } @@ -626,7 +624,7 @@ namespace Godot /// </summary> /// <param name="other">The other transform to compare.</param> /// <returns>Whether or not the matrices are approximately equal.</returns> - public bool IsEqualApprox(Transform2D other) + public readonly bool IsEqualApprox(Transform2D other) { return x.IsEqualApprox(other.x) && y.IsEqualApprox(other.y) && origin.IsEqualApprox(other.origin); } @@ -635,7 +633,7 @@ namespace Godot /// Serves as the hash function for <see cref="Transform2D"/>. /// </summary> /// <returns>A hash code for this transform.</returns> - public override int GetHashCode() + public override readonly int GetHashCode() { return x.GetHashCode() ^ y.GetHashCode() ^ origin.GetHashCode(); } @@ -644,7 +642,7 @@ namespace Godot /// Converts this <see cref="Transform2D"/> to a string. /// </summary> /// <returns>A string representation of this transform.</returns> - public override string ToString() + public override readonly string ToString() { return $"[X: {x}, Y: {y}, O: {origin}]"; } @@ -653,7 +651,7 @@ namespace Godot /// Converts this <see cref="Transform2D"/> to a string with the given <paramref name="format"/>. /// </summary> /// <returns>A string representation of this transform.</returns> - public string ToString(string format) + public readonly string ToString(string format) { return $"[X: {x.ToString(format)}, Y: {y.ToString(format)}, O: {origin.ToString(format)}]"; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs index 2f7891e7ef..39167bd116 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs @@ -37,7 +37,7 @@ namespace Godot /// </exception> public Vector3 this[int column] { - get + readonly get { switch (column) { @@ -83,7 +83,7 @@ namespace Godot /// <param name="row">Which row, the matrix vertical position.</param> public real_t this[int column, int row] { - get + readonly get { if (column == 3) { @@ -108,7 +108,7 @@ namespace Godot /// </summary> /// <seealso cref="Inverse"/> /// <returns>The inverse transformation matrix.</returns> - public Transform3D AffineInverse() + public readonly Transform3D AffineInverse() { Basis basisInv = basis.Inverse(); return new Transform3D(basisInv, basisInv * -origin); @@ -120,7 +120,7 @@ namespace Godot /// <param name="transform">The other transform.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The interpolated transform.</returns> - public Transform3D InterpolateWith(Transform3D transform, real_t weight) + public readonly Transform3D InterpolateWith(Transform3D transform, real_t weight) { Basis retBasis = basis.Lerp(transform.basis, weight); Vector3 retOrigin = origin.Lerp(transform.origin, weight); @@ -133,7 +133,7 @@ namespace Godot /// (no scaling, use <see cref="AffineInverse"/> for transforms with scaling). /// </summary> /// <returns>The inverse matrix.</returns> - public Transform3D Inverse() + public readonly Transform3D Inverse() { Basis basisTr = basis.Transposed(); return new Transform3D(basisTr, basisTr * -origin); @@ -164,7 +164,7 @@ namespace Godot /// and normalized axis vectors (scale of 1 or -1). /// </summary> /// <returns>The orthonormalized transform.</returns> - public Transform3D Orthonormalized() + public readonly Transform3D Orthonormalized() { return new Transform3D(basis.Orthonormalized(), origin); } @@ -192,7 +192,7 @@ namespace Godot /// <param name="axis">The axis to rotate around. Must be normalized.</param> /// <param name="angle">The angle to rotate, in radians.</param> /// <returns>The rotated transformation matrix.</returns> - public Transform3D RotatedLocal(Vector3 axis, real_t angle) + public readonly Transform3D RotatedLocal(Vector3 axis, real_t angle) { Basis tmpBasis = new Basis(axis, angle); return new Transform3D(basis * tmpBasis, origin); @@ -205,7 +205,7 @@ namespace Godot /// </summary> /// <param name="scale">The scale to introduce.</param> /// <returns>The scaled transformation matrix.</returns> - public Transform3D Scaled(Vector3 scale) + public readonly Transform3D Scaled(Vector3 scale) { return new Transform3D(basis.Scaled(scale), origin * scale); } @@ -217,7 +217,7 @@ namespace Godot /// </summary> /// <param name="scale">The scale to introduce.</param> /// <returns>The scaled transformation matrix.</returns> - public Transform3D ScaledLocal(Vector3 scale) + public readonly Transform3D ScaledLocal(Vector3 scale) { Basis tmpBasis = Basis.FromScale(scale); return new Transform3D(basis * tmpBasis, origin); @@ -230,7 +230,7 @@ namespace Godot /// <param name="transform">The other transform.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The interpolated transform.</returns> - public Transform3D SphericalInterpolateWith(Transform3D transform, real_t weight) + public readonly Transform3D SphericalInterpolateWith(Transform3D transform, real_t weight) { /* not sure if very "efficient" but good enough? */ @@ -281,7 +281,7 @@ namespace Godot /// </summary> /// <param name="offset">The offset to translate by.</param> /// <returns>The translated matrix.</returns> - public Transform3D Translated(Vector3 offset) + public readonly Transform3D Translated(Vector3 offset) { return new Transform3D(basis, origin + offset); } @@ -293,7 +293,7 @@ namespace Godot /// </summary> /// <param name="offset">The offset to translate by.</param> /// <returns>The translated matrix.</returns> - public Transform3D TranslatedLocal(Vector3 offset) + public readonly Transform3D TranslatedLocal(Vector3 offset) { return new Transform3D(basis, new Vector3 ( @@ -617,7 +617,7 @@ namespace Godot /// </summary> /// <param name="other">The other transform to compare.</param> /// <returns>Whether or not the matrices are approximately equal.</returns> - public bool IsEqualApprox(Transform3D other) + public readonly bool IsEqualApprox(Transform3D other) { return basis.IsEqualApprox(other.basis) && origin.IsEqualApprox(other.origin); } @@ -626,7 +626,7 @@ namespace Godot /// Serves as the hash function for <see cref="Transform3D"/>. /// </summary> /// <returns>A hash code for this transform.</returns> - public override int GetHashCode() + public override readonly int GetHashCode() { return basis.GetHashCode() ^ origin.GetHashCode(); } @@ -635,7 +635,7 @@ namespace Godot /// Converts this <see cref="Transform3D"/> to a string. /// </summary> /// <returns>A string representation of this transform.</returns> - public override string ToString() + public override readonly string ToString() { return $"[X: {basis.x}, Y: {basis.y}, Z: {basis.z}, O: {origin}]"; } @@ -644,7 +644,7 @@ namespace Godot /// Converts this <see cref="Transform3D"/> to a string with the given <paramref name="format"/>. /// </summary> /// <returns>A string representation of this transform.</returns> - public string ToString(string format) + public readonly string ToString(string format) { return $"[X: {basis.x.ToString(format)}, Y: {basis.y.ToString(format)}, Z: {basis.z.ToString(format)}, O: {origin.ToString(format)}]"; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Variant.cs index d354509dbf..49a363cef2 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Variant.cs @@ -109,16 +109,58 @@ public partial struct Variant : IDisposable public override string ToString() => AsString(); - public object? Obj - { - get + public object? Obj => + _obj ??= NativeVar.DangerousSelfRef.Type switch { - if (_obj == null) - _obj = Marshaling.ConvertVariantToManagedObject((godot_variant)NativeVar); - - return _obj; - } - } + Type.Bool => AsBool(), + Type.Int => AsInt64(), + Type.Float => AsDouble(), + Type.String => AsString(), + Type.Vector2 => AsVector2(), + Type.Vector2i => AsVector2i(), + Type.Rect2 => AsRect2(), + Type.Rect2i => AsRect2i(), + Type.Vector3 => AsVector3(), + Type.Vector3i => AsVector3i(), + Type.Transform2d => AsTransform2D(), + Type.Vector4 => AsVector4(), + Type.Vector4i => AsVector4i(), + Type.Plane => AsPlane(), + Type.Quaternion => AsQuaternion(), + Type.Aabb => AsAABB(), + Type.Basis => AsBasis(), + Type.Transform3d => AsTransform3D(), + Type.Projection => AsProjection(), + Type.Color => AsColor(), + Type.StringName => AsStringName(), + Type.NodePath => AsNodePath(), + Type.Rid => AsRID(), + Type.Object => AsGodotObject(), + Type.Callable => AsCallable(), + Type.Signal => AsSignalInfo(), + Type.Dictionary => AsGodotDictionary(), + Type.Array => AsGodotArray(), + Type.PackedByteArray => AsByteArray(), + Type.PackedInt32Array => AsInt32Array(), + Type.PackedInt64Array => AsInt64Array(), + Type.PackedFloat32Array => AsFloat32Array(), + Type.PackedFloat64Array => AsFloat64Array(), + Type.PackedStringArray => AsStringArray(), + Type.PackedVector2Array => AsVector2Array(), + Type.PackedVector3Array => AsVector3Array(), + Type.PackedColorArray => AsColorArray(), + Type.Nil => null, + Type.Max or _ => + throw new InvalidOperationException($"Invalid Variant type: {NativeVar.DangerousSelfRef.Type}"), + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant From<[MustBeVariant] T>(in T from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFrom(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T As<[MustBeVariant] T>() => + VariantUtils.ConvertTo<T>(NativeVar.DangerousSelfRef); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool AsBool() => diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index 87f397891e..c471eceded 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -48,7 +48,7 @@ namespace Godot /// </value> public real_t this[int index] { - get + readonly get { switch (index) { @@ -79,7 +79,7 @@ namespace Godot /// <summary> /// Helper method for deconstruction into a tuple. /// </summary> - public void Deconstruct(out real_t x, out real_t y) + public readonly void Deconstruct(out real_t x, out real_t y) { x = this.x; y = this.y; @@ -105,7 +105,7 @@ namespace Godot /// Returns a new vector with all components in absolute values (i.e. positive). /// </summary> /// <returns>A vector with <see cref="Mathf.Abs(real_t)"/> called on each component.</returns> - public Vector2 Abs() + public readonly Vector2 Abs() { return new Vector2(Mathf.Abs(x), Mathf.Abs(y)); } @@ -117,7 +117,7 @@ namespace Godot /// called with the vector's <see cref="y"/> and <see cref="x"/> as parameters: <c>Mathf.Atan2(v.y, v.x)</c>. /// </summary> /// <returns>The angle of this vector, in radians.</returns> - public real_t Angle() + public readonly real_t Angle() { return Mathf.Atan2(y, x); } @@ -127,7 +127,7 @@ namespace Godot /// </summary> /// <param name="to">The other vector to compare this vector to.</param> /// <returns>The angle between the two vectors, in radians.</returns> - public real_t AngleTo(Vector2 to) + public readonly real_t AngleTo(Vector2 to) { return Mathf.Atan2(Cross(to), Dot(to)); } @@ -137,7 +137,7 @@ namespace Godot /// </summary> /// <param name="to">The other vector to compare this vector to.</param> /// <returns>The angle between the two vectors, in radians.</returns> - public real_t AngleToPoint(Vector2 to) + public readonly real_t AngleToPoint(Vector2 to) { return Mathf.Atan2(y - to.y, x - to.x); } @@ -146,7 +146,7 @@ namespace Godot /// Returns the aspect ratio of this vector, the ratio of <see cref="x"/> to <see cref="y"/>. /// </summary> /// <returns>The <see cref="x"/> component divided by the <see cref="y"/> component.</returns> - public real_t Aspect() + public readonly real_t Aspect() { return x / y; } @@ -156,7 +156,7 @@ namespace Godot /// </summary> /// <param name="normal">The normal vector defining the plane to bounce off. Must be normalized.</param> /// <returns>The bounced vector.</returns> - public Vector2 Bounce(Vector2 normal) + public readonly Vector2 Bounce(Vector2 normal) { return -Reflect(normal); } @@ -165,7 +165,7 @@ namespace Godot /// Returns a new vector with all components rounded up (towards positive infinity). /// </summary> /// <returns>A vector with <see cref="Mathf.Ceil"/> called on each component.</returns> - public Vector2 Ceil() + public readonly Vector2 Ceil() { return new Vector2(Mathf.Ceil(x), Mathf.Ceil(y)); } @@ -178,7 +178,7 @@ namespace Godot /// <param name="min">The vector with minimum allowed values.</param> /// <param name="max">The vector with maximum allowed values.</param> /// <returns>The vector with all components clamped.</returns> - public Vector2 Clamp(Vector2 min, Vector2 max) + public readonly Vector2 Clamp(Vector2 min, Vector2 max) { return new Vector2 ( @@ -192,7 +192,7 @@ namespace Godot /// </summary> /// <param name="with">The other vector.</param> /// <returns>The cross product value.</returns> - public real_t Cross(Vector2 with) + public readonly real_t Cross(Vector2 with) { return (x * with.y) - (y * with.x); } @@ -206,7 +206,7 @@ namespace Godot /// <param name="postB">A vector after <paramref name="b"/>.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The interpolated vector.</returns> - public Vector2 CubicInterpolate(Vector2 b, Vector2 preA, Vector2 postB, real_t weight) + public readonly Vector2 CubicInterpolate(Vector2 b, Vector2 preA, Vector2 postB, real_t weight) { return new Vector2 ( @@ -229,7 +229,7 @@ namespace Godot /// <param name="preAT"></param> /// <param name="postBT"></param> /// <returns>The interpolated vector.</returns> - public Vector2 CubicInterpolateInTime(Vector2 b, Vector2 preA, Vector2 postB, real_t weight, real_t t, real_t preAT, real_t postBT) + public readonly Vector2 CubicInterpolateInTime(Vector2 b, Vector2 preA, Vector2 postB, real_t weight, real_t t, real_t preAT, real_t postBT) { return new Vector2 ( @@ -240,23 +240,37 @@ namespace Godot /// <summary> /// Returns the point at the given <paramref name="t"/> on a one-dimensional Bezier curve defined by this vector - /// and the given <paramref name="control1"/>, <paramref name="control2"/> and <paramref name="end"/> points. + /// and the given <paramref name="control1"/>, <paramref name="control2"/>, and <paramref name="end"/> points. /// </summary> /// <param name="control1">Control point that defines the bezier curve.</param> /// <param name="control2">Control point that defines the bezier curve.</param> /// <param name="end">The destination vector.</param> /// <param name="t">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The interpolated vector.</returns> - public Vector2 BezierInterpolate(Vector2 control1, Vector2 control2, Vector2 end, real_t t) + public readonly Vector2 BezierInterpolate(Vector2 control1, Vector2 control2, Vector2 end, real_t t) { - // Formula from Wikipedia article on Bezier curves - real_t omt = 1 - t; - real_t omt2 = omt * omt; - real_t omt3 = omt2 * omt; - real_t t2 = t * t; - real_t t3 = t2 * t; + return new Vector2 + ( + Mathf.BezierInterpolate(x, control1.x, control2.x, end.x, t), + Mathf.BezierInterpolate(y, control1.y, control2.y, end.y, t) + ); + } - return this * omt3 + control1 * omt2 * t * 3 + control2 * omt * t2 * 3 + end * t3; + /// <summary> + /// Returns the derivative at the given <paramref name="t"/> on the Bezier curve defined by this vector + /// and the given <paramref name="control1"/>, <paramref name="control2"/>, and <paramref name="end"/> points. + /// </summary> + /// <param name="control1">Control point that defines the bezier curve.</param> + /// <param name="control2">Control point that defines the bezier curve.</param> + /// <param name="end">The destination value for the interpolation.</param> + /// <param name="t">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting value of the interpolation.</returns> + public readonly Vector2 BezierDerivative(Vector2 control1, Vector2 control2, Vector2 end, real_t t) + { + return new Vector2( + Mathf.BezierDerivative(x, control1.x, control2.x, end.x, t), + Mathf.BezierDerivative(y, control1.y, control2.y, end.y, t) + ); } /// <summary> @@ -264,7 +278,7 @@ namespace Godot /// </summary> /// <param name="to">The other vector to point towards.</param> /// <returns>The direction from this vector to <paramref name="to"/>.</returns> - public Vector2 DirectionTo(Vector2 to) + public readonly Vector2 DirectionTo(Vector2 to) { return new Vector2(to.x - x, to.y - y).Normalized(); } @@ -276,7 +290,7 @@ namespace Godot /// </summary> /// <param name="to">The other vector to use.</param> /// <returns>The squared distance between the two vectors.</returns> - public real_t DistanceSquaredTo(Vector2 to) + public readonly real_t DistanceSquaredTo(Vector2 to) { return (x - to.x) * (x - to.x) + (y - to.y) * (y - to.y); } @@ -286,7 +300,7 @@ namespace Godot /// </summary> /// <param name="to">The other vector to use.</param> /// <returns>The distance between the two vectors.</returns> - public real_t DistanceTo(Vector2 to) + public readonly real_t DistanceTo(Vector2 to) { return Mathf.Sqrt((x - to.x) * (x - to.x) + (y - to.y) * (y - to.y)); } @@ -296,7 +310,7 @@ namespace Godot /// </summary> /// <param name="with">The other vector to use.</param> /// <returns>The dot product of the two vectors.</returns> - public real_t Dot(Vector2 with) + public readonly real_t Dot(Vector2 with) { return (x * with.x) + (y * with.y); } @@ -305,7 +319,7 @@ namespace Godot /// Returns a new vector with all components rounded down (towards negative infinity). /// </summary> /// <returns>A vector with <see cref="Mathf.Floor"/> called on each component.</returns> - public Vector2 Floor() + public readonly Vector2 Floor() { return new Vector2(Mathf.Floor(x), Mathf.Floor(y)); } @@ -314,7 +328,7 @@ namespace Godot /// Returns the inverse of this vector. This is the same as <c>new Vector2(1 / v.x, 1 / v.y)</c>. /// </summary> /// <returns>The inverse of this vector.</returns> - public Vector2 Inverse() + public readonly Vector2 Inverse() { return new Vector2(1 / x, 1 / y); } @@ -323,7 +337,7 @@ namespace Godot /// Returns <see langword="true"/> if the vector is normalized, and <see langword="false"/> otherwise. /// </summary> /// <returns>A <see langword="bool"/> indicating whether or not the vector is normalized.</returns> - public bool IsNormalized() + public readonly bool IsNormalized() { return Mathf.Abs(LengthSquared() - 1.0f) < Mathf.Epsilon; } @@ -333,7 +347,7 @@ namespace Godot /// </summary> /// <seealso cref="LengthSquared"/> /// <returns>The length of this vector.</returns> - public real_t Length() + public readonly real_t Length() { return Mathf.Sqrt((x * x) + (y * y)); } @@ -344,7 +358,7 @@ namespace Godot /// you need to compare vectors or need the squared length for some formula. /// </summary> /// <returns>The squared length of this vector.</returns> - public real_t LengthSquared() + public readonly real_t LengthSquared() { return (x * x) + (y * y); } @@ -356,7 +370,7 @@ namespace Godot /// <param name="to">The destination vector for interpolation.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The resulting vector of the interpolation.</returns> - public Vector2 Lerp(Vector2 to, real_t weight) + public readonly Vector2 Lerp(Vector2 to, real_t weight) { return new Vector2 ( @@ -374,7 +388,7 @@ namespace Godot /// A vector with components on the range of 0.0 to 1.0, representing the amount of interpolation. /// </param> /// <returns>The resulting vector of the interpolation.</returns> - public Vector2 Lerp(Vector2 to, Vector2 weight) + public readonly Vector2 Lerp(Vector2 to, Vector2 weight) { return new Vector2 ( @@ -388,7 +402,7 @@ namespace Godot /// </summary> /// <param name="length">The length to limit to.</param> /// <returns>The vector with its length limited.</returns> - public Vector2 LimitLength(real_t length = 1.0f) + public readonly Vector2 LimitLength(real_t length = 1.0f) { Vector2 v = this; real_t l = Length(); @@ -407,7 +421,7 @@ namespace Godot /// If both components are equal, this method returns <see cref="Axis.X"/>. /// </summary> /// <returns>The index of the highest axis.</returns> - public Axis MaxAxisIndex() + public readonly Axis MaxAxisIndex() { return x < y ? Axis.Y : Axis.X; } @@ -417,7 +431,7 @@ namespace Godot /// If both components are equal, this method returns <see cref="Axis.Y"/>. /// </summary> /// <returns>The index of the lowest axis.</returns> - public Axis MinAxisIndex() + public readonly Axis MinAxisIndex() { return x < y ? Axis.X : Axis.Y; } @@ -428,7 +442,7 @@ namespace Godot /// <param name="to">The vector to move towards.</param> /// <param name="delta">The amount to move towards by.</param> /// <returns>The resulting vector.</returns> - public Vector2 MoveToward(Vector2 to, real_t delta) + public readonly Vector2 MoveToward(Vector2 to, real_t delta) { Vector2 v = this; Vector2 vd = to - v; @@ -443,7 +457,7 @@ namespace Godot /// Returns the vector scaled to unit length. Equivalent to <c>v / v.Length()</c>. /// </summary> /// <returns>A normalized version of the vector.</returns> - public Vector2 Normalized() + public readonly Vector2 Normalized() { Vector2 v = this; v.Normalize(); @@ -458,7 +472,7 @@ namespace Godot /// <returns> /// A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by <paramref name="mod"/>. /// </returns> - public Vector2 PosMod(real_t mod) + public readonly Vector2 PosMod(real_t mod) { Vector2 v; v.x = Mathf.PosMod(x, mod); @@ -474,7 +488,7 @@ namespace Godot /// <returns> /// A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by <paramref name="modv"/>'s components. /// </returns> - public Vector2 PosMod(Vector2 modv) + public readonly Vector2 PosMod(Vector2 modv) { Vector2 v; v.x = Mathf.PosMod(x, modv.x); @@ -487,7 +501,7 @@ namespace Godot /// </summary> /// <param name="onNormal">The vector to project onto.</param> /// <returns>The projected vector.</returns> - public Vector2 Project(Vector2 onNormal) + public readonly Vector2 Project(Vector2 onNormal) { return onNormal * (Dot(onNormal) / onNormal.LengthSquared()); } @@ -497,7 +511,7 @@ namespace Godot /// </summary> /// <param name="normal">The normal vector defining the plane to reflect from. Must be normalized.</param> /// <returns>The reflected vector.</returns> - public Vector2 Reflect(Vector2 normal) + public readonly Vector2 Reflect(Vector2 normal) { #if DEBUG if (!normal.IsNormalized()) @@ -513,7 +527,7 @@ namespace Godot /// </summary> /// <param name="angle">The angle to rotate by, in radians.</param> /// <returns>The rotated vector.</returns> - public Vector2 Rotated(real_t angle) + public readonly Vector2 Rotated(real_t angle) { real_t sine = Mathf.Sin(angle); real_t cosi = Mathf.Cos(angle); @@ -527,7 +541,7 @@ namespace Godot /// with halfway cases rounded towards the nearest multiple of two. /// </summary> /// <returns>The rounded vector.</returns> - public Vector2 Round() + public readonly Vector2 Round() { return new Vector2(Mathf.Round(x), Mathf.Round(y)); } @@ -538,7 +552,7 @@ namespace Godot /// by calling <see cref="Mathf.Sign(real_t)"/> on each component. /// </summary> /// <returns>A vector with all components as either <c>1</c>, <c>-1</c>, or <c>0</c>.</returns> - public Vector2 Sign() + public readonly Vector2 Sign() { Vector2 v; v.x = Mathf.Sign(x); @@ -557,7 +571,7 @@ namespace Godot /// <param name="to">The destination vector for interpolation.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The resulting vector of the interpolation.</returns> - public Vector2 Slerp(Vector2 to, real_t weight) + public readonly Vector2 Slerp(Vector2 to, real_t weight) { real_t startLengthSquared = LengthSquared(); real_t endLengthSquared = to.LengthSquared(); @@ -577,7 +591,7 @@ namespace Godot /// </summary> /// <param name="normal">The normal vector defining the plane to slide on.</param> /// <returns>The slid vector.</returns> - public Vector2 Slide(Vector2 normal) + public readonly Vector2 Slide(Vector2 normal) { return this - (normal * Dot(normal)); } @@ -588,7 +602,7 @@ namespace Godot /// </summary> /// <param name="step">A vector value representing the step size to snap to.</param> /// <returns>The snapped vector.</returns> - public Vector2 Snapped(Vector2 step) + public readonly Vector2 Snapped(Vector2 step) { return new Vector2(Mathf.Snapped(x, step.x), Mathf.Snapped(y, step.y)); } @@ -598,7 +612,7 @@ namespace Godot /// compared to the original, with the same length. /// </summary> /// <returns>The perpendicular vector.</returns> - public Vector2 Orthogonal() + public readonly Vector2 Orthogonal() { return new Vector2(y, -x); } @@ -946,7 +960,7 @@ namespace Godot /// </summary> /// <param name="obj">The object to compare with.</param> /// <returns>Whether or not the vector and the object are equal.</returns> - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { return obj is Vector2 other && Equals(other); } @@ -958,7 +972,7 @@ namespace Godot /// </summary> /// <param name="other">The other vector.</param> /// <returns>Whether or not the vectors are exactly equal.</returns> - public bool Equals(Vector2 other) + public readonly bool Equals(Vector2 other) { return x == other.x && y == other.y; } @@ -969,7 +983,7 @@ namespace Godot /// </summary> /// <param name="other">The other vector to compare.</param> /// <returns>Whether or not the vectors are approximately equal.</returns> - public bool IsEqualApprox(Vector2 other) + public readonly bool IsEqualApprox(Vector2 other) { return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y); } @@ -978,7 +992,7 @@ namespace Godot /// Serves as the hash function for <see cref="Vector2"/>. /// </summary> /// <returns>A hash code for this vector.</returns> - public override int GetHashCode() + public override readonly int GetHashCode() { return y.GetHashCode() ^ x.GetHashCode(); } @@ -987,7 +1001,7 @@ namespace Godot /// Converts this <see cref="Vector2"/> to a string. /// </summary> /// <returns>A string representation of this vector.</returns> - public override string ToString() + public override readonly string ToString() { return $"({x}, {y})"; } @@ -996,7 +1010,7 @@ namespace Godot /// Converts this <see cref="Vector2"/> to a string with the given <paramref name="format"/>. /// </summary> /// <returns>A string representation of this vector.</returns> - public string ToString(string format) + public readonly string ToString(string format) { return $"({x.ToString(format)}, {y.ToString(format)})"; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs index bdadf696e3..08f2a3a8af 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs @@ -48,7 +48,7 @@ namespace Godot /// </value> public int this[int index] { - get + readonly get { switch (index) { @@ -79,7 +79,7 @@ namespace Godot /// <summary> /// Helper method for deconstruction into a tuple. /// </summary> - public void Deconstruct(out int x, out int y) + public readonly void Deconstruct(out int x, out int y) { x = this.x; y = this.y; @@ -89,7 +89,7 @@ namespace Godot /// Returns a new vector with all components in absolute values (i.e. positive). /// </summary> /// <returns>A vector with <see cref="Mathf.Abs(int)"/> called on each component.</returns> - public Vector2i Abs() + public readonly Vector2i Abs() { return new Vector2i(Mathf.Abs(x), Mathf.Abs(y)); } @@ -101,7 +101,7 @@ namespace Godot /// called with the vector's <see cref="y"/> and <see cref="x"/> as parameters: <c>Mathf.Atan2(v.y, v.x)</c>. /// </summary> /// <returns>The angle of this vector, in radians.</returns> - public real_t Angle() + public readonly real_t Angle() { return Mathf.Atan2(y, x); } @@ -111,7 +111,7 @@ namespace Godot /// </summary> /// <param name="to">The other vector to compare this vector to.</param> /// <returns>The angle between the two vectors, in radians.</returns> - public real_t AngleTo(Vector2i to) + public readonly real_t AngleTo(Vector2i to) { return Mathf.Atan2(Cross(to), Dot(to)); } @@ -121,7 +121,7 @@ namespace Godot /// </summary> /// <param name="to">The other vector to compare this vector to.</param> /// <returns>The angle between the two vectors, in radians.</returns> - public real_t AngleToPoint(Vector2i to) + public readonly real_t AngleToPoint(Vector2i to) { return Mathf.Atan2(y - to.y, x - to.x); } @@ -130,7 +130,7 @@ namespace Godot /// Returns the aspect ratio of this vector, the ratio of <see cref="x"/> to <see cref="y"/>. /// </summary> /// <returns>The <see cref="x"/> component divided by the <see cref="y"/> component.</returns> - public real_t Aspect() + public readonly real_t Aspect() { return x / (real_t)y; } @@ -143,7 +143,7 @@ namespace Godot /// <param name="min">The vector with minimum allowed values.</param> /// <param name="max">The vector with maximum allowed values.</param> /// <returns>The vector with all components clamped.</returns> - public Vector2i Clamp(Vector2i min, Vector2i max) + public readonly Vector2i Clamp(Vector2i min, Vector2i max) { return new Vector2i ( @@ -157,7 +157,7 @@ namespace Godot /// </summary> /// <param name="with">The other vector.</param> /// <returns>The cross product vector.</returns> - public int Cross(Vector2i with) + public readonly int Cross(Vector2i with) { return x * with.y - y * with.x; } @@ -169,7 +169,7 @@ namespace Godot /// </summary> /// <param name="to">The other vector to use.</param> /// <returns>The squared distance between the two vectors.</returns> - public int DistanceSquaredTo(Vector2i to) + public readonly int DistanceSquaredTo(Vector2i to) { return (to - this).LengthSquared(); } @@ -179,7 +179,7 @@ namespace Godot /// </summary> /// <param name="to">The other vector to use.</param> /// <returns>The distance between the two vectors.</returns> - public real_t DistanceTo(Vector2i to) + public readonly real_t DistanceTo(Vector2i to) { return (to - this).Length(); } @@ -189,7 +189,7 @@ namespace Godot /// </summary> /// <param name="with">The other vector to use.</param> /// <returns>The dot product of the two vectors.</returns> - public int Dot(Vector2i with) + public readonly int Dot(Vector2i with) { return x * with.x + y * with.y; } @@ -199,7 +199,7 @@ namespace Godot /// </summary> /// <seealso cref="LengthSquared"/> /// <returns>The length of this vector.</returns> - public real_t Length() + public readonly real_t Length() { int x2 = x * x; int y2 = y * y; @@ -213,7 +213,7 @@ namespace Godot /// you need to compare vectors or need the squared length for some formula. /// </summary> /// <returns>The squared length of this vector.</returns> - public int LengthSquared() + public readonly int LengthSquared() { int x2 = x * x; int y2 = y * y; @@ -226,7 +226,7 @@ namespace Godot /// If both components are equal, this method returns <see cref="Axis.X"/>. /// </summary> /// <returns>The index of the highest axis.</returns> - public Axis MaxAxisIndex() + public readonly Axis MaxAxisIndex() { return x < y ? Axis.Y : Axis.X; } @@ -236,7 +236,7 @@ namespace Godot /// If both components are equal, this method returns <see cref="Axis.Y"/>. /// </summary> /// <returns>The index of the lowest axis.</returns> - public Axis MinAxisIndex() + public readonly Axis MinAxisIndex() { return x < y ? Axis.X : Axis.Y; } @@ -249,7 +249,7 @@ namespace Godot /// <returns> /// A vector with each component <see cref="Mathf.PosMod(int, int)"/> by <paramref name="mod"/>. /// </returns> - public Vector2i PosMod(int mod) + public readonly Vector2i PosMod(int mod) { Vector2i v = this; v.x = Mathf.PosMod(v.x, mod); @@ -265,7 +265,7 @@ namespace Godot /// <returns> /// A vector with each component <see cref="Mathf.PosMod(int, int)"/> by <paramref name="modv"/>'s components. /// </returns> - public Vector2i PosMod(Vector2i modv) + public readonly Vector2i PosMod(Vector2i modv) { Vector2i v = this; v.x = Mathf.PosMod(v.x, modv.x); @@ -279,7 +279,7 @@ namespace Godot /// by calling <see cref="Mathf.Sign(int)"/> on each component. /// </summary> /// <returns>A vector with all components as either <c>1</c>, <c>-1</c>, or <c>0</c>.</returns> - public Vector2i Sign() + public readonly Vector2i Sign() { Vector2i v = this; v.x = Mathf.Sign(v.x); @@ -292,7 +292,7 @@ namespace Godot /// compared to the original, with the same length. /// </summary> /// <returns>The perpendicular vector.</returns> - public Vector2i Orthogonal() + public readonly Vector2i Orthogonal() { return new Vector2i(y, -x); } @@ -665,7 +665,7 @@ namespace Godot /// </summary> /// <param name="obj">The object to compare with.</param> /// <returns>Whether or not the vector and the object are equal.</returns> - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { return obj is Vector2i other && Equals(other); } @@ -675,7 +675,7 @@ namespace Godot /// </summary> /// <param name="other">The other vector.</param> /// <returns>Whether or not the vectors are equal.</returns> - public bool Equals(Vector2i other) + public readonly bool Equals(Vector2i other) { return x == other.x && y == other.y; } @@ -684,7 +684,7 @@ namespace Godot /// Serves as the hash function for <see cref="Vector2i"/>. /// </summary> /// <returns>A hash code for this vector.</returns> - public override int GetHashCode() + public override readonly int GetHashCode() { return y.GetHashCode() ^ x.GetHashCode(); } @@ -693,7 +693,7 @@ namespace Godot /// Converts this <see cref="Vector2i"/> to a string. /// </summary> /// <returns>A string representation of this vector.</returns> - public override string ToString() + public override readonly string ToString() { return $"({x}, {y})"; } @@ -702,7 +702,7 @@ namespace Godot /// Converts this <see cref="Vector2i"/> to a string with the given <paramref name="format"/>. /// </summary> /// <returns>A string representation of this vector.</returns> - public string ToString(string format) + public readonly string ToString(string format) { return $"({x.ToString(format)}, {y.ToString(format)})"; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index 6649f3b784..fefdee33a5 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -58,7 +58,7 @@ namespace Godot /// </value> public real_t this[int index] { - get + readonly get { switch (index) { @@ -94,7 +94,7 @@ namespace Godot /// <summary> /// Helper method for deconstruction into a tuple. /// </summary> - public void Deconstruct(out real_t x, out real_t y, out real_t z) + public readonly void Deconstruct(out real_t x, out real_t y, out real_t z) { x = this.x; y = this.y; @@ -122,7 +122,7 @@ namespace Godot /// Returns a new vector with all components in absolute values (i.e. positive). /// </summary> /// <returns>A vector with <see cref="Mathf.Abs(real_t)"/> called on each component.</returns> - public Vector3 Abs() + public readonly Vector3 Abs() { return new Vector3(Mathf.Abs(x), Mathf.Abs(y), Mathf.Abs(z)); } @@ -132,7 +132,7 @@ namespace Godot /// </summary> /// <param name="to">The other vector to compare this vector to.</param> /// <returns>The unsigned angle between the two vectors, in radians.</returns> - public real_t AngleTo(Vector3 to) + public readonly real_t AngleTo(Vector3 to) { return Mathf.Atan2(Cross(to).Length(), Dot(to)); } @@ -142,7 +142,7 @@ namespace Godot /// </summary> /// <param name="normal">The normal vector defining the plane to bounce off. Must be normalized.</param> /// <returns>The bounced vector.</returns> - public Vector3 Bounce(Vector3 normal) + public readonly Vector3 Bounce(Vector3 normal) { return -Reflect(normal); } @@ -151,7 +151,7 @@ namespace Godot /// Returns a new vector with all components rounded up (towards positive infinity). /// </summary> /// <returns>A vector with <see cref="Mathf.Ceil"/> called on each component.</returns> - public Vector3 Ceil() + public readonly Vector3 Ceil() { return new Vector3(Mathf.Ceil(x), Mathf.Ceil(y), Mathf.Ceil(z)); } @@ -164,7 +164,7 @@ namespace Godot /// <param name="min">The vector with minimum allowed values.</param> /// <param name="max">The vector with maximum allowed values.</param> /// <returns>The vector with all components clamped.</returns> - public Vector3 Clamp(Vector3 min, Vector3 max) + public readonly Vector3 Clamp(Vector3 min, Vector3 max) { return new Vector3 ( @@ -179,7 +179,7 @@ namespace Godot /// </summary> /// <param name="with">The other vector.</param> /// <returns>The cross product vector.</returns> - public Vector3 Cross(Vector3 with) + public readonly Vector3 Cross(Vector3 with) { return new Vector3 ( @@ -198,7 +198,7 @@ namespace Godot /// <param name="postB">A vector after <paramref name="b"/>.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The interpolated vector.</returns> - public Vector3 CubicInterpolate(Vector3 b, Vector3 preA, Vector3 postB, real_t weight) + public readonly Vector3 CubicInterpolate(Vector3 b, Vector3 preA, Vector3 postB, real_t weight) { return new Vector3 ( @@ -222,7 +222,7 @@ namespace Godot /// <param name="preAT"></param> /// <param name="postBT"></param> /// <returns>The interpolated vector.</returns> - public Vector3 CubicInterpolateInTime(Vector3 b, Vector3 preA, Vector3 postB, real_t weight, real_t t, real_t preAT, real_t postBT) + public readonly Vector3 CubicInterpolateInTime(Vector3 b, Vector3 preA, Vector3 postB, real_t weight, real_t t, real_t preAT, real_t postBT) { return new Vector3 ( @@ -234,23 +234,39 @@ namespace Godot /// <summary> /// Returns the point at the given <paramref name="t"/> on a one-dimensional Bezier curve defined by this vector - /// and the given <paramref name="control1"/>, <paramref name="control2"/> and <paramref name="end"/> points. + /// and the given <paramref name="control1"/>, <paramref name="control2"/>, and <paramref name="end"/> points. /// </summary> /// <param name="control1">Control point that defines the bezier curve.</param> /// <param name="control2">Control point that defines the bezier curve.</param> /// <param name="end">The destination vector.</param> /// <param name="t">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The interpolated vector.</returns> - public Vector3 BezierInterpolate(Vector3 control1, Vector3 control2, Vector3 end, real_t t) + public readonly Vector3 BezierInterpolate(Vector3 control1, Vector3 control2, Vector3 end, real_t t) { - // Formula from Wikipedia article on Bezier curves - real_t omt = 1 - t; - real_t omt2 = omt * omt; - real_t omt3 = omt2 * omt; - real_t t2 = t * t; - real_t t3 = t2 * t; + return new Vector3 + ( + Mathf.BezierInterpolate(x, control1.x, control2.x, end.x, t), + Mathf.BezierInterpolate(y, control1.y, control2.y, end.y, t), + Mathf.BezierInterpolate(z, control1.z, control2.z, end.z, t) + ); + } - return this * omt3 + control1 * omt2 * t * 3 + control2 * omt * t2 * 3 + end * t3; + /// <summary> + /// Returns the derivative at the given <paramref name="t"/> on the Bezier curve defined by this vector + /// and the given <paramref name="control1"/>, <paramref name="control2"/>, and <paramref name="end"/> points. + /// </summary> + /// <param name="control1">Control point that defines the bezier curve.</param> + /// <param name="control2">Control point that defines the bezier curve.</param> + /// <param name="end">The destination value for the interpolation.</param> + /// <param name="t">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> + /// <returns>The resulting value of the interpolation.</returns> + public readonly Vector3 BezierDerivative(Vector3 control1, Vector3 control2, Vector3 end, real_t t) + { + return new Vector3( + Mathf.BezierDerivative(x, control1.x, control2.x, end.x, t), + Mathf.BezierDerivative(y, control1.y, control2.y, end.y, t), + Mathf.BezierDerivative(z, control1.z, control2.z, end.y, t) + ); } /// <summary> @@ -258,7 +274,7 @@ namespace Godot /// </summary> /// <param name="to">The other vector to point towards.</param> /// <returns>The direction from this vector to <paramref name="to"/>.</returns> - public Vector3 DirectionTo(Vector3 to) + public readonly Vector3 DirectionTo(Vector3 to) { return new Vector3(to.x - x, to.y - y, to.z - z).Normalized(); } @@ -270,7 +286,7 @@ namespace Godot /// </summary> /// <param name="to">The other vector to use.</param> /// <returns>The squared distance between the two vectors.</returns> - public real_t DistanceSquaredTo(Vector3 to) + public readonly real_t DistanceSquaredTo(Vector3 to) { return (to - this).LengthSquared(); } @@ -281,7 +297,7 @@ namespace Godot /// <seealso cref="DistanceSquaredTo(Vector3)"/> /// <param name="to">The other vector to use.</param> /// <returns>The distance between the two vectors.</returns> - public real_t DistanceTo(Vector3 to) + public readonly real_t DistanceTo(Vector3 to) { return (to - this).Length(); } @@ -291,7 +307,7 @@ namespace Godot /// </summary> /// <param name="with">The other vector to use.</param> /// <returns>The dot product of the two vectors.</returns> - public real_t Dot(Vector3 with) + public readonly real_t Dot(Vector3 with) { return (x * with.x) + (y * with.y) + (z * with.z); } @@ -300,7 +316,7 @@ namespace Godot /// Returns a new vector with all components rounded down (towards negative infinity). /// </summary> /// <returns>A vector with <see cref="Mathf.Floor"/> called on each component.</returns> - public Vector3 Floor() + public readonly Vector3 Floor() { return new Vector3(Mathf.Floor(x), Mathf.Floor(y), Mathf.Floor(z)); } @@ -309,7 +325,7 @@ namespace Godot /// Returns the inverse of this vector. This is the same as <c>new Vector3(1 / v.x, 1 / v.y, 1 / v.z)</c>. /// </summary> /// <returns>The inverse of this vector.</returns> - public Vector3 Inverse() + public readonly Vector3 Inverse() { return new Vector3(1 / x, 1 / y, 1 / z); } @@ -318,7 +334,7 @@ namespace Godot /// Returns <see langword="true"/> if the vector is normalized, and <see langword="false"/> otherwise. /// </summary> /// <returns>A <see langword="bool"/> indicating whether or not the vector is normalized.</returns> - public bool IsNormalized() + public readonly bool IsNormalized() { return Mathf.Abs(LengthSquared() - 1.0f) < Mathf.Epsilon; } @@ -328,7 +344,7 @@ namespace Godot /// </summary> /// <seealso cref="LengthSquared"/> /// <returns>The length of this vector.</returns> - public real_t Length() + public readonly real_t Length() { real_t x2 = x * x; real_t y2 = y * y; @@ -343,7 +359,7 @@ namespace Godot /// you need to compare vectors or need the squared length for some formula. /// </summary> /// <returns>The squared length of this vector.</returns> - public real_t LengthSquared() + public readonly real_t LengthSquared() { real_t x2 = x * x; real_t y2 = y * y; @@ -359,7 +375,7 @@ namespace Godot /// <param name="to">The destination vector for interpolation.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The resulting vector of the interpolation.</returns> - public Vector3 Lerp(Vector3 to, real_t weight) + public readonly Vector3 Lerp(Vector3 to, real_t weight) { return new Vector3 ( @@ -376,7 +392,7 @@ namespace Godot /// <param name="to">The destination vector for interpolation.</param> /// <param name="weight">A vector with components on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The resulting vector of the interpolation.</returns> - public Vector3 Lerp(Vector3 to, Vector3 weight) + public readonly Vector3 Lerp(Vector3 to, Vector3 weight) { return new Vector3 ( @@ -391,7 +407,7 @@ namespace Godot /// </summary> /// <param name="length">The length to limit to.</param> /// <returns>The vector with its length limited.</returns> - public Vector3 LimitLength(real_t length = 1.0f) + public readonly Vector3 LimitLength(real_t length = 1.0f) { Vector3 v = this; real_t l = Length(); @@ -410,7 +426,7 @@ namespace Godot /// If all components are equal, this method returns <see cref="Axis.X"/>. /// </summary> /// <returns>The index of the highest axis.</returns> - public Axis MaxAxisIndex() + public readonly Axis MaxAxisIndex() { return x < y ? (y < z ? Axis.Z : Axis.Y) : (x < z ? Axis.Z : Axis.X); } @@ -420,7 +436,7 @@ namespace Godot /// If all components are equal, this method returns <see cref="Axis.Z"/>. /// </summary> /// <returns>The index of the lowest axis.</returns> - public Axis MinAxisIndex() + public readonly Axis MinAxisIndex() { return x < y ? (x < z ? Axis.X : Axis.Z) : (y < z ? Axis.Y : Axis.Z); } @@ -431,7 +447,7 @@ namespace Godot /// <param name="to">The vector to move towards.</param> /// <param name="delta">The amount to move towards by.</param> /// <returns>The resulting vector.</returns> - public Vector3 MoveToward(Vector3 to, real_t delta) + public readonly Vector3 MoveToward(Vector3 to, real_t delta) { Vector3 v = this; Vector3 vd = to - v; @@ -446,7 +462,7 @@ namespace Godot /// Returns the vector scaled to unit length. Equivalent to <c>v / v.Length()</c>. /// </summary> /// <returns>A normalized version of the vector.</returns> - public Vector3 Normalized() + public readonly Vector3 Normalized() { Vector3 v = this; v.Normalize(); @@ -458,7 +474,7 @@ namespace Godot /// </summary> /// <param name="with">The other vector.</param> /// <returns>A <see cref="Basis"/> representing the outer product matrix.</returns> - public Basis Outer(Vector3 with) + public readonly Basis Outer(Vector3 with) { return new Basis( x * with.x, x * with.y, x * with.z, @@ -475,7 +491,7 @@ namespace Godot /// <returns> /// A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by <paramref name="mod"/>. /// </returns> - public Vector3 PosMod(real_t mod) + public readonly Vector3 PosMod(real_t mod) { Vector3 v; v.x = Mathf.PosMod(x, mod); @@ -492,7 +508,7 @@ namespace Godot /// <returns> /// A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by <paramref name="modv"/>'s components. /// </returns> - public Vector3 PosMod(Vector3 modv) + public readonly Vector3 PosMod(Vector3 modv) { Vector3 v; v.x = Mathf.PosMod(x, modv.x); @@ -506,7 +522,7 @@ namespace Godot /// </summary> /// <param name="onNormal">The vector to project onto.</param> /// <returns>The projected vector.</returns> - public Vector3 Project(Vector3 onNormal) + public readonly Vector3 Project(Vector3 onNormal) { return onNormal * (Dot(onNormal) / onNormal.LengthSquared()); } @@ -516,7 +532,7 @@ namespace Godot /// </summary> /// <param name="normal">The normal vector defining the plane to reflect from. Must be normalized.</param> /// <returns>The reflected vector.</returns> - public Vector3 Reflect(Vector3 normal) + public readonly Vector3 Reflect(Vector3 normal) { #if DEBUG if (!normal.IsNormalized()) @@ -534,7 +550,7 @@ namespace Godot /// <param name="axis">The vector to rotate around. Must be normalized.</param> /// <param name="angle">The angle to rotate by, in radians.</param> /// <returns>The rotated vector.</returns> - public Vector3 Rotated(Vector3 axis, real_t angle) + public readonly Vector3 Rotated(Vector3 axis, real_t angle) { #if DEBUG if (!axis.IsNormalized()) @@ -550,7 +566,7 @@ namespace Godot /// with halfway cases rounded towards the nearest multiple of two. /// </summary> /// <returns>The rounded vector.</returns> - public Vector3 Round() + public readonly Vector3 Round() { return new Vector3(Mathf.Round(x), Mathf.Round(y), Mathf.Round(z)); } @@ -561,7 +577,7 @@ namespace Godot /// by calling <see cref="Mathf.Sign(real_t)"/> on each component. /// </summary> /// <returns>A vector with all components as either <c>1</c>, <c>-1</c>, or <c>0</c>.</returns> - public Vector3 Sign() + public readonly Vector3 Sign() { Vector3 v; v.x = Mathf.Sign(x); @@ -579,7 +595,7 @@ namespace Godot /// <param name="to">The other vector to compare this vector to.</param> /// <param name="axis">The reference axis to use for the angle sign.</param> /// <returns>The signed angle between the two vectors, in radians.</returns> - public real_t SignedAngleTo(Vector3 to, Vector3 axis) + public readonly real_t SignedAngleTo(Vector3 to, Vector3 axis) { Vector3 crossTo = Cross(to); real_t unsignedAngle = Mathf.Atan2(crossTo.Length(), Dot(to)); @@ -598,7 +614,7 @@ namespace Godot /// <param name="to">The destination vector for interpolation.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The resulting vector of the interpolation.</returns> - public Vector3 Slerp(Vector3 to, real_t weight) + public readonly Vector3 Slerp(Vector3 to, real_t weight) { real_t startLengthSquared = LengthSquared(); real_t endLengthSquared = to.LengthSquared(); @@ -618,7 +634,7 @@ namespace Godot /// </summary> /// <param name="normal">The normal vector defining the plane to slide on.</param> /// <returns>The slid vector.</returns> - public Vector3 Slide(Vector3 normal) + public readonly Vector3 Slide(Vector3 normal) { return this - (normal * Dot(normal)); } @@ -629,7 +645,7 @@ namespace Godot /// </summary> /// <param name="step">A vector value representing the step size to snap to.</param> /// <returns>The snapped vector.</returns> - public Vector3 Snapped(Vector3 step) + public readonly Vector3 Snapped(Vector3 step) { return new Vector3 ( @@ -1015,7 +1031,7 @@ namespace Godot /// </summary> /// <param name="obj">The object to compare with.</param> /// <returns>Whether or not the vector and the object are equal.</returns> - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { return obj is Vector3 other && Equals(other); } @@ -1027,7 +1043,7 @@ namespace Godot /// </summary> /// <param name="other">The other vector.</param> /// <returns>Whether or not the vectors are exactly equal.</returns> - public bool Equals(Vector3 other) + public readonly bool Equals(Vector3 other) { return x == other.x && y == other.y && z == other.z; } @@ -1038,7 +1054,7 @@ namespace Godot /// </summary> /// <param name="other">The other vector to compare.</param> /// <returns>Whether or not the vectors are approximately equal.</returns> - public bool IsEqualApprox(Vector3 other) + public readonly bool IsEqualApprox(Vector3 other) { return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y) && Mathf.IsEqualApprox(z, other.z); } @@ -1047,7 +1063,7 @@ namespace Godot /// Serves as the hash function for <see cref="Vector3"/>. /// </summary> /// <returns>A hash code for this vector.</returns> - public override int GetHashCode() + public override readonly int GetHashCode() { return y.GetHashCode() ^ x.GetHashCode() ^ z.GetHashCode(); } @@ -1056,7 +1072,7 @@ namespace Godot /// Converts this <see cref="Vector3"/> to a string. /// </summary> /// <returns>A string representation of this vector.</returns> - public override string ToString() + public override readonly string ToString() { return $"({x}, {y}, {z})"; } @@ -1065,7 +1081,7 @@ namespace Godot /// Converts this <see cref="Vector3"/> to a string with the given <paramref name="format"/>. /// </summary> /// <returns>A string representation of this vector.</returns> - public string ToString(string format) + public readonly string ToString(string format) { return $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)})"; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs index e88a043cb3..e631a9f443 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs @@ -58,7 +58,7 @@ namespace Godot /// </value> public int this[int index] { - get + readonly get { switch (index) { @@ -94,7 +94,7 @@ namespace Godot /// <summary> /// Helper method for deconstruction into a tuple. /// </summary> - public void Deconstruct(out int x, out int y, out int z) + public readonly void Deconstruct(out int x, out int y, out int z) { x = this.x; y = this.y; @@ -105,7 +105,7 @@ namespace Godot /// Returns a new vector with all components in absolute values (i.e. positive). /// </summary> /// <returns>A vector with <see cref="Mathf.Abs(int)"/> called on each component.</returns> - public Vector3i Abs() + public readonly Vector3i Abs() { return new Vector3i(Mathf.Abs(x), Mathf.Abs(y), Mathf.Abs(z)); } @@ -118,7 +118,7 @@ namespace Godot /// <param name="min">The vector with minimum allowed values.</param> /// <param name="max">The vector with maximum allowed values.</param> /// <returns>The vector with all components clamped.</returns> - public Vector3i Clamp(Vector3i min, Vector3i max) + public readonly Vector3i Clamp(Vector3i min, Vector3i max) { return new Vector3i ( @@ -135,7 +135,7 @@ namespace Godot /// </summary> /// <param name="to">The other vector to use.</param> /// <returns>The squared distance between the two vectors.</returns> - public int DistanceSquaredTo(Vector3i to) + public readonly int DistanceSquaredTo(Vector3i to) { return (to - this).LengthSquared(); } @@ -146,7 +146,7 @@ namespace Godot /// <seealso cref="DistanceSquaredTo(Vector3i)"/> /// <param name="to">The other vector to use.</param> /// <returns>The distance between the two vectors.</returns> - public real_t DistanceTo(Vector3i to) + public readonly real_t DistanceTo(Vector3i to) { return (to - this).Length(); } @@ -156,7 +156,7 @@ namespace Godot /// </summary> /// <param name="with">The other vector to use.</param> /// <returns>The dot product of the two vectors.</returns> - public int Dot(Vector3i with) + public readonly int Dot(Vector3i with) { return x * with.x + y * with.y + z * with.z; } @@ -166,7 +166,7 @@ namespace Godot /// </summary> /// <seealso cref="LengthSquared"/> /// <returns>The length of this vector.</returns> - public real_t Length() + public readonly real_t Length() { int x2 = x * x; int y2 = y * y; @@ -181,7 +181,7 @@ namespace Godot /// you need to compare vectors or need the squared length for some formula. /// </summary> /// <returns>The squared length of this vector.</returns> - public int LengthSquared() + public readonly int LengthSquared() { int x2 = x * x; int y2 = y * y; @@ -195,7 +195,7 @@ namespace Godot /// If all components are equal, this method returns <see cref="Axis.X"/>. /// </summary> /// <returns>The index of the highest axis.</returns> - public Axis MaxAxisIndex() + public readonly Axis MaxAxisIndex() { return x < y ? (y < z ? Axis.Z : Axis.Y) : (x < z ? Axis.Z : Axis.X); } @@ -205,7 +205,7 @@ namespace Godot /// If all components are equal, this method returns <see cref="Axis.Z"/>. /// </summary> /// <returns>The index of the lowest axis.</returns> - public Axis MinAxisIndex() + public readonly Axis MinAxisIndex() { return x < y ? (x < z ? Axis.X : Axis.Z) : (y < z ? Axis.Y : Axis.Z); } @@ -218,7 +218,7 @@ namespace Godot /// <returns> /// A vector with each component <see cref="Mathf.PosMod(int, int)"/> by <paramref name="mod"/>. /// </returns> - public Vector3i PosMod(int mod) + public readonly Vector3i PosMod(int mod) { Vector3i v = this; v.x = Mathf.PosMod(v.x, mod); @@ -235,7 +235,7 @@ namespace Godot /// <returns> /// A vector with each component <see cref="Mathf.PosMod(int, int)"/> by <paramref name="modv"/>'s components. /// </returns> - public Vector3i PosMod(Vector3i modv) + public readonly Vector3i PosMod(Vector3i modv) { Vector3i v = this; v.x = Mathf.PosMod(v.x, modv.x); @@ -250,7 +250,7 @@ namespace Godot /// by calling <see cref="Mathf.Sign(int)"/> on each component. /// </summary> /// <returns>A vector with all components as either <c>1</c>, <c>-1</c>, or <c>0</c>.</returns> - public Vector3i Sign() + public readonly Vector3i Sign() { Vector3i v = this; v.x = Mathf.Sign(v.x); @@ -674,7 +674,7 @@ namespace Godot /// </summary> /// <param name="obj">The object to compare with.</param> /// <returns>Whether or not the vector and the object are equal.</returns> - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { return obj is Vector3i other && Equals(other); } @@ -684,7 +684,7 @@ namespace Godot /// </summary> /// <param name="other">The other vector.</param> /// <returns>Whether or not the vectors are equal.</returns> - public bool Equals(Vector3i other) + public readonly bool Equals(Vector3i other) { return x == other.x && y == other.y && z == other.z; } @@ -693,7 +693,7 @@ namespace Godot /// Serves as the hash function for <see cref="Vector3i"/>. /// </summary> /// <returns>A hash code for this vector.</returns> - public override int GetHashCode() + public override readonly int GetHashCode() { return y.GetHashCode() ^ x.GetHashCode() ^ z.GetHashCode(); } @@ -702,7 +702,7 @@ namespace Godot /// Converts this <see cref="Vector3i"/> to a string. /// </summary> /// <returns>A string representation of this vector.</returns> - public override string ToString() + public override readonly string ToString() { return $"({x}, {y}, {z})"; } @@ -711,7 +711,7 @@ namespace Godot /// Converts this <see cref="Vector3i"/> to a string with the given <paramref name="format"/>. /// </summary> /// <returns>A string representation of this vector.</returns> - public string ToString(string format) + public readonly string ToString(string format) { return $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)})"; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs index e2da41ff47..3191e8adc0 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs @@ -68,7 +68,7 @@ namespace Godot /// </value> public real_t this[int index] { - get + readonly get { switch (index) { @@ -109,7 +109,7 @@ namespace Godot /// <summary> /// Helper method for deconstruction into a tuple. /// </summary> - public void Deconstruct(out real_t x, out real_t y, out real_t z, out real_t w) + public readonly void Deconstruct(out real_t x, out real_t y, out real_t z, out real_t w) { x = this.x; y = this.y; @@ -139,7 +139,7 @@ namespace Godot /// Returns a new vector with all components in absolute values (i.e. positive). /// </summary> /// <returns>A vector with <see cref="Mathf.Abs(real_t)"/> called on each component.</returns> - public Vector4 Abs() + public readonly Vector4 Abs() { return new Vector4(Mathf.Abs(x), Mathf.Abs(y), Mathf.Abs(z), Mathf.Abs(w)); } @@ -148,7 +148,7 @@ namespace Godot /// Returns a new vector with all components rounded up (towards positive infinity). /// </summary> /// <returns>A vector with <see cref="Mathf.Ceil"/> called on each component.</returns> - public Vector4 Ceil() + public readonly Vector4 Ceil() { return new Vector4(Mathf.Ceil(x), Mathf.Ceil(y), Mathf.Ceil(z), Mathf.Ceil(w)); } @@ -161,7 +161,7 @@ namespace Godot /// <param name="min">The vector with minimum allowed values.</param> /// <param name="max">The vector with maximum allowed values.</param> /// <returns>The vector with all components clamped.</returns> - public Vector4 Clamp(Vector4 min, Vector4 max) + public readonly Vector4 Clamp(Vector4 min, Vector4 max) { return new Vector4 ( @@ -181,7 +181,7 @@ namespace Godot /// <param name="postB">A vector after <paramref name="b"/>.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The interpolated vector.</returns> - public Vector4 CubicInterpolate(Vector4 b, Vector4 preA, Vector4 postB, real_t weight) + public readonly Vector4 CubicInterpolate(Vector4 b, Vector4 preA, Vector4 postB, real_t weight) { return new Vector4 ( @@ -206,7 +206,7 @@ namespace Godot /// <param name="preAT"></param> /// <param name="postBT"></param> /// <returns>The interpolated vector.</returns> - public Vector4 CubicInterpolateInTime(Vector4 b, Vector4 preA, Vector4 postB, real_t weight, real_t t, real_t preAT, real_t postBT) + public readonly Vector4 CubicInterpolateInTime(Vector4 b, Vector4 preA, Vector4 postB, real_t weight, real_t t, real_t preAT, real_t postBT) { return new Vector4 ( @@ -222,7 +222,7 @@ namespace Godot /// </summary> /// <param name="to">The other vector to point towards.</param> /// <returns>The direction from this vector to <paramref name="to"/>.</returns> - public Vector4 DirectionTo(Vector4 to) + public readonly Vector4 DirectionTo(Vector4 to) { Vector4 ret = new Vector4(to.x - x, to.y - y, to.z - z, to.w - w); ret.Normalize(); @@ -236,7 +236,7 @@ namespace Godot /// </summary> /// <param name="to">The other vector to use.</param> /// <returns>The squared distance between the two vectors.</returns> - public real_t DistanceSquaredTo(Vector4 to) + public readonly real_t DistanceSquaredTo(Vector4 to) { return (to - this).LengthSquared(); } @@ -246,7 +246,7 @@ namespace Godot /// </summary> /// <param name="to">The other vector to use.</param> /// <returns>The distance between the two vectors.</returns> - public real_t DistanceTo(Vector4 to) + public readonly real_t DistanceTo(Vector4 to) { return (to - this).Length(); } @@ -256,7 +256,7 @@ namespace Godot /// </summary> /// <param name="with">The other vector to use.</param> /// <returns>The dot product of the two vectors.</returns> - public real_t Dot(Vector4 with) + public readonly real_t Dot(Vector4 with) { return (x * with.x) + (y * with.y) + (z * with.z) + (w * with.w); } @@ -265,7 +265,7 @@ namespace Godot /// Returns a new vector with all components rounded down (towards negative infinity). /// </summary> /// <returns>A vector with <see cref="Mathf.Floor"/> called on each component.</returns> - public Vector4 Floor() + public readonly Vector4 Floor() { return new Vector4(Mathf.Floor(x), Mathf.Floor(y), Mathf.Floor(z), Mathf.Floor(w)); } @@ -274,7 +274,7 @@ namespace Godot /// Returns the inverse of this vector. This is the same as <c>new Vector4(1 / v.x, 1 / v.y, 1 / v.z, 1 / v.w)</c>. /// </summary> /// <returns>The inverse of this vector.</returns> - public Vector4 Inverse() + public readonly Vector4 Inverse() { return new Vector4(1 / x, 1 / y, 1 / z, 1 / w); } @@ -283,7 +283,7 @@ namespace Godot /// Returns <see langword="true"/> if the vector is normalized, and <see langword="false"/> otherwise. /// </summary> /// <returns>A <see langword="bool"/> indicating whether or not the vector is normalized.</returns> - public bool IsNormalized() + public readonly bool IsNormalized() { return Mathf.Abs(LengthSquared() - 1.0f) < Mathf.Epsilon; } @@ -293,7 +293,7 @@ namespace Godot /// </summary> /// <seealso cref="LengthSquared"/> /// <returns>The length of this vector.</returns> - public real_t Length() + public readonly real_t Length() { real_t x2 = x * x; real_t y2 = y * y; @@ -309,7 +309,7 @@ namespace Godot /// you need to compare vectors or need the squared length for some formula. /// </summary> /// <returns>The squared length of this vector.</returns> - public real_t LengthSquared() + public readonly real_t LengthSquared() { real_t x2 = x * x; real_t y2 = y * y; @@ -326,7 +326,7 @@ namespace Godot /// <param name="to">The destination vector for interpolation.</param> /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param> /// <returns>The resulting vector of the interpolation.</returns> - public Vector4 Lerp(Vector4 to, real_t weight) + public readonly Vector4 Lerp(Vector4 to, real_t weight) { return new Vector4 ( @@ -342,7 +342,7 @@ namespace Godot /// If all components are equal, this method returns <see cref="Axis.X"/>. /// </summary> /// <returns>The index of the highest axis.</returns> - public Axis MaxAxisIndex() + public readonly Axis MaxAxisIndex() { int max_index = 0; real_t max_value = x; @@ -362,7 +362,7 @@ namespace Godot /// If all components are equal, this method returns <see cref="Axis.W"/>. /// </summary> /// <returns>The index of the lowest axis.</returns> - public Axis MinAxisIndex() + public readonly Axis MinAxisIndex() { int min_index = 0; real_t min_value = x; @@ -381,7 +381,7 @@ namespace Godot /// Returns the vector scaled to unit length. Equivalent to <c>v / v.Length()</c>. /// </summary> /// <returns>A normalized version of the vector.</returns> - public Vector4 Normalized() + public readonly Vector4 Normalized() { Vector4 v = this; v.Normalize(); @@ -396,7 +396,7 @@ namespace Godot /// <returns> /// A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by <paramref name="mod"/>. /// </returns> - public Vector4 PosMod(real_t mod) + public readonly Vector4 PosMod(real_t mod) { return new Vector4( Mathf.PosMod(x, mod), @@ -414,7 +414,7 @@ namespace Godot /// <returns> /// A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by <paramref name="modv"/>'s components. /// </returns> - public Vector4 PosMod(Vector4 modv) + public readonly Vector4 PosMod(Vector4 modv) { return new Vector4( Mathf.PosMod(x, modv.x), @@ -429,7 +429,7 @@ namespace Godot /// with halfway cases rounded towards the nearest multiple of two. /// </summary> /// <returns>The rounded vector.</returns> - public Vector4 Round() + public readonly Vector4 Round() { return new Vector4(Mathf.Round(x), Mathf.Round(y), Mathf.Round(z), Mathf.Round(w)); } @@ -440,7 +440,7 @@ namespace Godot /// by calling <see cref="Mathf.Sign(real_t)"/> on each component. /// </summary> /// <returns>A vector with all components as either <c>1</c>, <c>-1</c>, or <c>0</c>.</returns> - public Vector4 Sign() + public readonly Vector4 Sign() { Vector4 v; v.x = Mathf.Sign(x); @@ -456,7 +456,7 @@ namespace Godot /// </summary> /// <param name="step">A vector value representing the step size to snap to.</param> /// <returns>The snapped vector.</returns> - public Vector4 Snapped(Vector4 step) + public readonly Vector4 Snapped(Vector4 step) { return new Vector4( Mathf.Snapped(x, step.x), @@ -828,7 +828,7 @@ namespace Godot /// </summary> /// <param name="obj">The object to compare with.</param> /// <returns>Whether or not the vector and the object are equal.</returns> - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { return obj is Vector4 other && Equals(other); } @@ -840,7 +840,7 @@ namespace Godot /// </summary> /// <param name="other">The other vector.</param> /// <returns>Whether or not the vectors are exactly equal.</returns> - public bool Equals(Vector4 other) + public readonly bool Equals(Vector4 other) { return x == other.x && y == other.y && z == other.z && w == other.w; } @@ -851,7 +851,7 @@ namespace Godot /// </summary> /// <param name="other">The other vector to compare.</param> /// <returns>Whether or not the vectors are approximately equal.</returns> - public bool IsEqualApprox(Vector4 other) + public readonly bool IsEqualApprox(Vector4 other) { return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y) && Mathf.IsEqualApprox(z, other.z) && Mathf.IsEqualApprox(w, other.w); } @@ -860,7 +860,7 @@ namespace Godot /// Serves as the hash function for <see cref="Vector4"/>. /// </summary> /// <returns>A hash code for this vector.</returns> - public override int GetHashCode() + public override readonly int GetHashCode() { return y.GetHashCode() ^ x.GetHashCode() ^ z.GetHashCode() ^ w.GetHashCode(); } @@ -878,7 +878,7 @@ namespace Godot /// Converts this <see cref="Vector4"/> to a string with the given <paramref name="format"/>. /// </summary> /// <returns>A string representation of this vector.</returns> - public string ToString(string format) + public readonly string ToString(string format) { return $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)}, {w.ToString(format)})"; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4i.cs index 4b1bb3ba19..8146991fd7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4i.cs @@ -68,7 +68,7 @@ namespace Godot /// </value> public int this[int index] { - get + readonly get { switch (index) { @@ -109,7 +109,7 @@ namespace Godot /// <summary> /// Helper method for deconstruction into a tuple. /// </summary> - public void Deconstruct(out int x, out int y, out int z, out int w) + public readonly void Deconstruct(out int x, out int y, out int z, out int w) { x = this.x; y = this.y; @@ -121,7 +121,7 @@ namespace Godot /// Returns a new vector with all components in absolute values (i.e. positive). /// </summary> /// <returns>A vector with <see cref="Mathf.Abs(int)"/> called on each component.</returns> - public Vector4i Abs() + public readonly Vector4i Abs() { return new Vector4i(Mathf.Abs(x), Mathf.Abs(y), Mathf.Abs(z), Mathf.Abs(w)); } @@ -134,7 +134,7 @@ namespace Godot /// <param name="min">The vector with minimum allowed values.</param> /// <param name="max">The vector with maximum allowed values.</param> /// <returns>The vector with all components clamped.</returns> - public Vector4i Clamp(Vector4i min, Vector4i max) + public readonly Vector4i Clamp(Vector4i min, Vector4i max) { return new Vector4i ( @@ -150,7 +150,7 @@ namespace Godot /// </summary> /// <seealso cref="LengthSquared"/> /// <returns>The length of this vector.</returns> - public real_t Length() + public readonly real_t Length() { int x2 = x * x; int y2 = y * y; @@ -166,7 +166,7 @@ namespace Godot /// you need to compare vectors or need the squared length for some formula. /// </summary> /// <returns>The squared length of this vector.</returns> - public int LengthSquared() + public readonly int LengthSquared() { int x2 = x * x; int y2 = y * y; @@ -181,7 +181,7 @@ namespace Godot /// If all components are equal, this method returns <see cref="Axis.X"/>. /// </summary> /// <returns>The index of the highest axis.</returns> - public Axis MaxAxisIndex() + public readonly Axis MaxAxisIndex() { int max_index = 0; int max_value = x; @@ -201,7 +201,7 @@ namespace Godot /// If all components are equal, this method returns <see cref="Axis.W"/>. /// </summary> /// <returns>The index of the lowest axis.</returns> - public Axis MinAxisIndex() + public readonly Axis MinAxisIndex() { int min_index = 0; int min_value = x; @@ -222,7 +222,7 @@ namespace Godot /// by calling <see cref="Mathf.Sign(int)"/> on each component. /// </summary> /// <returns>A vector with all components as either <c>1</c>, <c>-1</c>, or <c>0</c>.</returns> - public Vector4i Sign() + public readonly Vector4i Sign() { return new Vector4i(Mathf.Sign(x), Mathf.Sign(y), Mathf.Sign(z), Mathf.Sign(w)); } @@ -627,7 +627,7 @@ namespace Godot /// </summary> /// <param name="obj">The object to compare with.</param> /// <returns>Whether or not the vector and the object are equal.</returns> - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { return obj is Vector4i other && Equals(other); } @@ -637,7 +637,7 @@ namespace Godot /// </summary> /// <param name="other">The other vector.</param> /// <returns>Whether or not the vectors are equal.</returns> - public bool Equals(Vector4i other) + public readonly bool Equals(Vector4i other) { return x == other.x && y == other.y && z == other.z && w == other.w; } @@ -646,7 +646,7 @@ namespace Godot /// Serves as the hash function for <see cref="Vector4i"/>. /// </summary> /// <returns>A hash code for this vector.</returns> - public override int GetHashCode() + public override readonly int GetHashCode() { return y.GetHashCode() ^ x.GetHashCode() ^ z.GetHashCode() ^ w.GetHashCode(); } @@ -655,7 +655,7 @@ namespace Godot /// Converts this <see cref="Vector4i"/> to a string. /// </summary> /// <returns>A string representation of this vector.</returns> - public override string ToString() + public override readonly string ToString() { return $"({x}, {y}, {z}, {w})"; } @@ -664,7 +664,7 @@ namespace Godot /// Converts this <see cref="Vector4i"/> to a string with the given <paramref name="format"/>. /// </summary> /// <returns>A string representation of this vector.</returns> - public string ToString(string format) + public readonly string ToString(string format) { return $"({x.ToString(format)}, {y.ToString(format)}, {z.ToString(format)}), {w.ToString(format)})"; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index e3fb254f49..503e5abe37 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -101,9 +101,8 @@ <Compile Include="Core\NativeInterop\InteropUtils.cs" /> <Compile Include="Core\NativeInterop\NativeFuncs.extended.cs" /> <Compile Include="Core\NativeInterop\NativeVariantPtrArgs.cs" /> - <Compile Include="Core\NativeInterop\VariantConversionCallbacks.cs" /> - <Compile Include="Core\NativeInterop\VariantSpanHelpers.cs" /> <Compile Include="Core\NativeInterop\VariantUtils.cs" /> + <Compile Include="Core\NativeInterop\VariantUtils.generic.cs" /> <Compile Include="Core\NodePath.cs" /> <Compile Include="Core\Object.base.cs" /> <Compile Include="Core\Object.exceptions.cs" /> @@ -123,6 +122,7 @@ <Compile Include="Core\StringName.cs" /> <Compile Include="Core\Transform2D.cs" /> <Compile Include="Core\Transform3D.cs" /> + <Compile Include="Core\Variant.cs" /> <Compile Include="Core\Vector2.cs" /> <Compile Include="Core\Vector2i.cs" /> <Compile Include="Core\Vector3.cs" /> @@ -131,7 +131,6 @@ <Compile Include="Core\Vector4i.cs" /> <Compile Include="GlobalUsings.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> - <Compile Include="Variant.cs" /> </ItemGroup> <!-- We import a props file with auto-generated includes. This works well with Rider. diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index e20a88076a..338e5a0147 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -1067,30 +1067,6 @@ void godotsharp_dictionary_to_string(const Dictionary *p_self, String *r_str) { *r_str = Variant(*p_self).operator String(); } -void godotsharp_string_md5_buffer(const String *p_self, PackedByteArray *r_md5_buffer) { - memnew_placement(r_md5_buffer, PackedByteArray(p_self->md5_buffer())); -} - -void godotsharp_string_md5_text(const String *p_self, String *r_md5_text) { - memnew_placement(r_md5_text, String(p_self->md5_text())); -} - -int32_t godotsharp_string_rfind(const String *p_self, const String *p_what, int32_t p_from) { - return p_self->rfind(*p_what, p_from); -} - -int32_t godotsharp_string_rfindn(const String *p_self, const String *p_what, int32_t p_from) { - return p_self->rfindn(*p_what, p_from); -} - -void godotsharp_string_sha256_buffer(const String *p_self, PackedByteArray *r_sha256_buffer) { - memnew_placement(r_sha256_buffer, PackedByteArray(p_self->sha256_buffer())); -} - -void godotsharp_string_sha256_text(const String *p_self, String *r_sha256_text) { - memnew_placement(r_sha256_text, String(p_self->sha256_text())); -} - void godotsharp_string_simplify_path(const String *p_self, String *r_simplified_path) { memnew_placement(r_simplified_path, String(p_self->simplify_path())); } @@ -1473,12 +1449,6 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_dictionary_duplicate, (void *)godotsharp_dictionary_remove_key, (void *)godotsharp_dictionary_to_string, - (void *)godotsharp_string_md5_buffer, - (void *)godotsharp_string_md5_text, - (void *)godotsharp_string_rfind, - (void *)godotsharp_string_rfindn, - (void *)godotsharp_string_sha256_buffer, - (void *)godotsharp_string_sha256_text, (void *)godotsharp_string_simplify_path, (void *)godotsharp_string_to_camel_case, (void *)godotsharp_string_to_pascal_case, diff --git a/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml index 7ed6255a62..af7c345f15 100644 --- a/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml +++ b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml @@ -17,7 +17,7 @@ <param index="0" name="filter" type="Callable" /> <description> Adds a peer visibility filter for this synchronizer. - [code]filter[/code] should take a peer id [int] and return a [bool]. + [code]filter[/code] should take a peer ID [int] and return a [bool]. </description> </method> <method name="get_visibility_for" qualifiers="const"> diff --git a/modules/multiplayer/editor/editor_network_profiler.cpp b/modules/multiplayer/editor/editor_network_profiler.cpp new file mode 100644 index 0000000000..cce22b9084 --- /dev/null +++ b/modules/multiplayer/editor/editor_network_profiler.cpp @@ -0,0 +1,347 @@ +/*************************************************************************/ +/* editor_network_profiler.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "editor_network_profiler.h" + +#include "core/os/os.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" + +void EditorNetworkProfiler::_bind_methods() { + ADD_SIGNAL(MethodInfo("enable_profiling", PropertyInfo(Variant::BOOL, "enable"))); + ADD_SIGNAL(MethodInfo("open_request", PropertyInfo(Variant::STRING, "path"))); +} + +void EditorNetworkProfiler::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + node_icon = get_theme_icon(SNAME("Node"), SNAME("EditorIcons")); + if (activate->is_pressed()) { + activate->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons"))); + } else { + activate->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons"))); + } + clear_button->set_icon(get_theme_icon(SNAME("Clear"), SNAME("EditorIcons"))); + incoming_bandwidth_text->set_right_icon(get_theme_icon(SNAME("ArrowDown"), SNAME("EditorIcons"))); + outgoing_bandwidth_text->set_right_icon(get_theme_icon(SNAME("ArrowUp"), SNAME("EditorIcons"))); + + // This needs to be done here to set the faded color when the profiler is first opened + incoming_bandwidth_text->add_theme_color_override("font_uneditable_color", get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, 0.5)); + outgoing_bandwidth_text->add_theme_color_override("font_uneditable_color", get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, 0.5)); + } break; + } +} + +void EditorNetworkProfiler::_refresh() { + if (!dirty) { + return; + } + dirty = false; + refresh_rpc_data(); + refresh_replication_data(); +} + +void EditorNetworkProfiler::refresh_rpc_data() { + counters_display->clear(); + + TreeItem *root = counters_display->create_item(); + int cols = counters_display->get_columns(); + + for (const KeyValue<ObjectID, RPCNodeInfo> &E : rpc_data) { + TreeItem *node = counters_display->create_item(root); + + for (int j = 0; j < cols; ++j) { + node->set_text_alignment(j, j > 0 ? HORIZONTAL_ALIGNMENT_RIGHT : HORIZONTAL_ALIGNMENT_LEFT); + } + + node->set_text(0, E.value.node_path); + node->set_text(1, E.value.incoming_rpc == 0 ? "-" : vformat(TTR("%d (%s)"), E.value.incoming_rpc, String::humanize_size(E.value.incoming_size))); + node->set_text(2, E.value.outgoing_rpc == 0 ? "-" : vformat(TTR("%d (%s)"), E.value.outgoing_rpc, String::humanize_size(E.value.outgoing_size))); + } +} + +void EditorNetworkProfiler::refresh_replication_data() { + replication_display->clear(); + + TreeItem *root = replication_display->create_item(); + + for (const KeyValue<ObjectID, SyncInfo> &E : sync_data) { + // Ensure the nodes have at least a temporary cache. + ObjectID ids[3] = { E.value.synchronizer, E.value.config, E.value.root_node }; + for (uint32_t i = 0; i < 3; i++) { + const ObjectID &id = ids[i]; + if (!node_data.has(id)) { + missing_node_data.insert(id); + node_data[id] = NodeInfo(id); + } + } + + TreeItem *node = replication_display->create_item(root); + + const NodeInfo &root_info = node_data[E.value.root_node]; + const NodeInfo &sync_info = node_data[E.value.synchronizer]; + const NodeInfo &cfg_info = node_data[E.value.config]; + + node->set_text(0, root_info.path.get_file()); + node->set_icon(0, has_theme_icon(root_info.type, SNAME("EditorIcons")) ? get_theme_icon(root_info.type, SNAME("EditorIcons")) : node_icon); + node->set_tooltip_text(0, root_info.path); + + node->set_text(1, sync_info.path.get_file()); + node->set_icon(1, get_theme_icon("MultiplayerSynchronizer", SNAME("EditorIcons"))); + node->set_tooltip_text(1, sync_info.path); + + int cfg_idx = cfg_info.path.find("::"); + if (cfg_info.path.begins_with("res://") && ResourceLoader::exists(cfg_info.path) && cfg_idx > 0) { + String res_idstr = cfg_info.path.substr(cfg_idx + 2).replace("SceneReplicationConfig_", ""); + String scene_path = cfg_info.path.substr(0, cfg_idx); + node->set_text(2, vformat("%s (%s)", res_idstr, scene_path.get_file())); + node->add_button(2, get_theme_icon(SNAME("InstanceOptions"), SNAME("EditorIcons"))); + node->set_tooltip_text(2, cfg_info.path); + node->set_metadata(2, scene_path); + } else { + node->set_text(2, cfg_info.path); + node->set_metadata(2, ""); + } + + node->set_text(3, vformat("%d - %d", E.value.incoming_syncs, E.value.outgoing_syncs)); + node->set_text(4, vformat("%d - %d", E.value.incoming_size, E.value.outgoing_size)); + } +} + +Array EditorNetworkProfiler::pop_missing_node_data() { + Array out; + for (const ObjectID &id : missing_node_data) { + out.push_back(id); + } + missing_node_data.clear(); + return out; +} + +void EditorNetworkProfiler::add_node_data(const NodeInfo &p_info) { + ERR_FAIL_COND(!node_data.has(p_info.id)); + node_data[p_info.id] = p_info; + dirty = true; +} + +void EditorNetworkProfiler::_activate_pressed() { + if (activate->is_pressed()) { + refresh_timer->start(); + activate->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons"))); + activate->set_text(TTR("Stop")); + } else { + refresh_timer->stop(); + activate->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons"))); + activate->set_text(TTR("Start")); + } + emit_signal(SNAME("enable_profiling"), activate->is_pressed()); +} + +void EditorNetworkProfiler::_clear_pressed() { + rpc_data.clear(); + sync_data.clear(); + node_data.clear(); + missing_node_data.clear(); + set_bandwidth(0, 0); + refresh_rpc_data(); + refresh_replication_data(); +} + +void EditorNetworkProfiler::_replication_button_clicked(TreeItem *p_item, int p_column, int p_idx, MouseButton p_button) { + if (!p_item) { + return; + } + String meta = p_item->get_metadata(p_column); + if (meta.size() && ResourceLoader::exists(meta)) { + emit_signal("open_request", meta); + } +} + +void EditorNetworkProfiler::add_rpc_frame_data(const RPCNodeInfo &p_frame) { + dirty = true; + if (!rpc_data.has(p_frame.node)) { + rpc_data.insert(p_frame.node, p_frame); + } else { + rpc_data[p_frame.node].incoming_rpc += p_frame.incoming_rpc; + rpc_data[p_frame.node].outgoing_rpc += p_frame.outgoing_rpc; + } + if (p_frame.incoming_rpc) { + rpc_data[p_frame.node].incoming_size = p_frame.incoming_size / p_frame.incoming_rpc; + } + if (p_frame.outgoing_rpc) { + rpc_data[p_frame.node].outgoing_size = p_frame.outgoing_size / p_frame.outgoing_rpc; + } +} + +void EditorNetworkProfiler::add_sync_frame_data(const SyncInfo &p_frame) { + dirty = true; + if (!sync_data.has(p_frame.synchronizer)) { + sync_data[p_frame.synchronizer] = p_frame; + } else { + sync_data[p_frame.synchronizer].incoming_syncs += p_frame.incoming_syncs; + sync_data[p_frame.synchronizer].outgoing_syncs += p_frame.outgoing_syncs; + } + SyncInfo &info = sync_data[p_frame.synchronizer]; + if (info.incoming_syncs) { + info.incoming_size = p_frame.incoming_size / p_frame.incoming_syncs; + } + if (info.outgoing_syncs) { + info.outgoing_size = p_frame.outgoing_size / p_frame.outgoing_syncs; + } +} + +void EditorNetworkProfiler::set_bandwidth(int p_incoming, int p_outgoing) { + incoming_bandwidth_text->set_text(vformat(TTR("%s/s"), String::humanize_size(p_incoming))); + outgoing_bandwidth_text->set_text(vformat(TTR("%s/s"), String::humanize_size(p_outgoing))); + + // Make labels more prominent when the bandwidth is greater than 0 to attract user attention + incoming_bandwidth_text->add_theme_color_override( + "font_uneditable_color", + get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, p_incoming > 0 ? 1 : 0.5)); + outgoing_bandwidth_text->add_theme_color_override( + "font_uneditable_color", + get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, p_outgoing > 0 ? 1 : 0.5)); +} + +bool EditorNetworkProfiler::is_profiling() { + return activate->is_pressed(); +} + +EditorNetworkProfiler::EditorNetworkProfiler() { + HBoxContainer *hb = memnew(HBoxContainer); + hb->add_theme_constant_override("separation", 8 * EDSCALE); + add_child(hb); + + activate = memnew(Button); + activate->set_toggle_mode(true); + activate->set_text(TTR("Start")); + activate->connect("pressed", callable_mp(this, &EditorNetworkProfiler::_activate_pressed)); + hb->add_child(activate); + + clear_button = memnew(Button); + clear_button->set_text(TTR("Clear")); + clear_button->connect("pressed", callable_mp(this, &EditorNetworkProfiler::_clear_pressed)); + hb->add_child(clear_button); + + hb->add_spacer(); + + Label *lb = memnew(Label); + lb->set_text(TTR("Down")); + hb->add_child(lb); + + incoming_bandwidth_text = memnew(LineEdit); + incoming_bandwidth_text->set_editable(false); + incoming_bandwidth_text->set_custom_minimum_size(Size2(120, 0) * EDSCALE); + incoming_bandwidth_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); + hb->add_child(incoming_bandwidth_text); + + Control *down_up_spacer = memnew(Control); + down_up_spacer->set_custom_minimum_size(Size2(30, 0) * EDSCALE); + hb->add_child(down_up_spacer); + + lb = memnew(Label); + lb->set_text(TTR("Up")); + hb->add_child(lb); + + outgoing_bandwidth_text = memnew(LineEdit); + outgoing_bandwidth_text->set_editable(false); + outgoing_bandwidth_text->set_custom_minimum_size(Size2(120, 0) * EDSCALE); + outgoing_bandwidth_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); + hb->add_child(outgoing_bandwidth_text); + + // Set initial texts in the incoming/outgoing bandwidth labels + set_bandwidth(0, 0); + + HSplitContainer *sc = memnew(HSplitContainer); + add_child(sc); + sc->set_v_size_flags(SIZE_EXPAND_FILL); + sc->set_h_size_flags(SIZE_EXPAND_FILL); + sc->set_split_offset(100 * EDSCALE); + + // RPC + counters_display = memnew(Tree); + counters_display->set_custom_minimum_size(Size2(320, 0) * EDSCALE); + counters_display->set_v_size_flags(SIZE_EXPAND_FILL); + counters_display->set_h_size_flags(SIZE_EXPAND_FILL); + counters_display->set_hide_folding(true); + counters_display->set_hide_root(true); + counters_display->set_columns(3); + counters_display->set_column_titles_visible(true); + counters_display->set_column_title(0, TTR("Node")); + counters_display->set_column_expand(0, true); + counters_display->set_column_clip_content(0, true); + counters_display->set_column_custom_minimum_width(0, 60 * EDSCALE); + counters_display->set_column_title(1, TTR("Incoming RPC")); + counters_display->set_column_expand(1, false); + counters_display->set_column_clip_content(1, true); + counters_display->set_column_custom_minimum_width(1, 120 * EDSCALE); + counters_display->set_column_title(2, TTR("Outgoing RPC")); + counters_display->set_column_expand(2, false); + counters_display->set_column_clip_content(2, true); + counters_display->set_column_custom_minimum_width(2, 120 * EDSCALE); + sc->add_child(counters_display); + + // Replication + replication_display = memnew(Tree); + replication_display->set_custom_minimum_size(Size2(320, 0) * EDSCALE); + replication_display->set_v_size_flags(SIZE_EXPAND_FILL); + replication_display->set_h_size_flags(SIZE_EXPAND_FILL); + replication_display->set_hide_folding(true); + replication_display->set_hide_root(true); + replication_display->set_columns(5); + replication_display->set_column_titles_visible(true); + replication_display->set_column_title(0, TTR("Root")); + replication_display->set_column_expand(0, true); + replication_display->set_column_clip_content(0, true); + replication_display->set_column_custom_minimum_width(0, 80 * EDSCALE); + replication_display->set_column_title(1, TTR("Synchronizer")); + replication_display->set_column_expand(1, true); + replication_display->set_column_clip_content(1, true); + replication_display->set_column_custom_minimum_width(1, 80 * EDSCALE); + replication_display->set_column_title(2, TTR("Config")); + replication_display->set_column_expand(2, true); + replication_display->set_column_clip_content(2, true); + replication_display->set_column_custom_minimum_width(2, 80 * EDSCALE); + replication_display->set_column_title(3, TTR("Count")); + replication_display->set_column_expand(3, false); + replication_display->set_column_clip_content(3, true); + replication_display->set_column_custom_minimum_width(3, 80 * EDSCALE); + replication_display->set_column_title(4, TTR("Size")); + replication_display->set_column_expand(4, false); + replication_display->set_column_clip_content(4, true); + replication_display->set_column_custom_minimum_width(4, 80 * EDSCALE); + replication_display->connect("button_clicked", callable_mp(this, &EditorNetworkProfiler::_replication_button_clicked)); + sc->add_child(replication_display); + + refresh_timer = memnew(Timer); + refresh_timer->set_wait_time(0.5); + refresh_timer->connect("timeout", callable_mp(this, &EditorNetworkProfiler::_refresh)); + add_child(refresh_timer); +} diff --git a/modules/multiplayer/editor/editor_network_profiler.h b/modules/multiplayer/editor/editor_network_profiler.h new file mode 100644 index 0000000000..630747d988 --- /dev/null +++ b/modules/multiplayer/editor/editor_network_profiler.h @@ -0,0 +1,101 @@ +/*************************************************************************/ +/* editor_network_profiler.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef EDITOR_NETWORK_PROFILER_H +#define EDITOR_NETWORK_PROFILER_H + +#include "scene/debugger/scene_debugger.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/label.h" +#include "scene/gui/split_container.h" +#include "scene/gui/tree.h" + +#include "../multiplayer_debugger.h" + +class EditorNetworkProfiler : public VBoxContainer { + GDCLASS(EditorNetworkProfiler, VBoxContainer) + +public: + struct NodeInfo { + ObjectID id; + String type; + String path; + + NodeInfo() {} + NodeInfo(const ObjectID &p_id) { + id = p_id; + path = String::num_int64(p_id); + } + }; + +private: + using RPCNodeInfo = MultiplayerDebugger::RPCNodeInfo; + using SyncInfo = MultiplayerDebugger::SyncInfo; + + bool dirty = false; + Timer *refresh_timer = nullptr; + Button *activate = nullptr; + Button *clear_button = nullptr; + Tree *counters_display = nullptr; + LineEdit *incoming_bandwidth_text = nullptr; + LineEdit *outgoing_bandwidth_text = nullptr; + Tree *replication_display = nullptr; + + HashMap<ObjectID, RPCNodeInfo> rpc_data; + HashMap<ObjectID, SyncInfo> sync_data; + HashMap<ObjectID, NodeInfo> node_data; + HashSet<ObjectID> missing_node_data; + Ref<Texture2D> node_icon; + + void _activate_pressed(); + void _clear_pressed(); + void _refresh(); + void _replication_button_clicked(TreeItem *p_item, int p_column, int p_idx, MouseButton p_button); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void refresh_rpc_data(); + void refresh_replication_data(); + + Array pop_missing_node_data(); + void add_node_data(const NodeInfo &p_info); + void add_rpc_frame_data(const RPCNodeInfo &p_frame); + void add_sync_frame_data(const SyncInfo &p_frame); + void set_bandwidth(int p_incoming, int p_outgoing); + bool is_profiling(); + + EditorNetworkProfiler(); +}; + +#endif // EDITOR_NETWORK_PROFILER_H diff --git a/modules/multiplayer/editor/multiplayer_editor_plugin.cpp b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp new file mode 100644 index 0000000000..c5cf3e6f24 --- /dev/null +++ b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp @@ -0,0 +1,175 @@ +/*************************************************************************/ +/* multiplayer_editor_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "multiplayer_editor_plugin.h" + +#include "../multiplayer_synchronizer.h" +#include "editor_network_profiler.h" +#include "replication_editor.h" + +#include "editor/editor_node.h" + +void MultiplayerEditorDebugger::_bind_methods() { + ADD_SIGNAL(MethodInfo("open_request", PropertyInfo(Variant::STRING, "path"))); +} + +bool MultiplayerEditorDebugger::has_capture(const String &p_capture) const { + return p_capture == "multiplayer"; +} + +void MultiplayerEditorDebugger::_open_request(const String &p_path) { + emit_signal("open_request", p_path); +} + +bool MultiplayerEditorDebugger::capture(const String &p_message, const Array &p_data, int p_session) { + ERR_FAIL_COND_V(!profilers.has(p_session), false); + EditorNetworkProfiler *profiler = profilers[p_session]; + if (p_message == "multiplayer:rpc") { + MultiplayerDebugger::RPCFrame frame; + frame.deserialize(p_data); + for (int i = 0; i < frame.infos.size(); i++) { + profiler->add_rpc_frame_data(frame.infos[i]); + } + return true; + } else if (p_message == "multiplayer:syncs") { + MultiplayerDebugger::ReplicationFrame frame; + frame.deserialize(p_data); + for (const KeyValue<ObjectID, MultiplayerDebugger::SyncInfo> &E : frame.infos) { + profiler->add_sync_frame_data(E.value); + } + Array missing = profiler->pop_missing_node_data(); + if (missing.size()) { + // Asks for the object information. + get_session(p_session)->send_message("multiplayer:cache", missing); + } + return true; + } else if (p_message == "multiplayer:cache") { + ERR_FAIL_COND_V(p_data.size() % 3, false); + for (int i = 0; i < p_data.size(); i += 3) { + EditorNetworkProfiler::NodeInfo info; + info.id = p_data[i].operator ObjectID(); + info.type = p_data[i + 1].operator String(); + info.path = p_data[i + 2].operator String(); + profiler->add_node_data(info); + } + return true; + } else if (p_message == "multiplayer:bandwidth") { + ERR_FAIL_COND_V(p_data.size() < 2, false); + profiler->set_bandwidth(p_data[0], p_data[1]); + return true; + } + return false; +} + +void MultiplayerEditorDebugger::_profiler_activate(bool p_enable, int p_session_id) { + Ref<EditorDebuggerSession> session = get_session(p_session_id); + ERR_FAIL_COND(session.is_null()); + session->toggle_profiler("multiplayer:bandwidth", p_enable); + session->toggle_profiler("multiplayer:rpc", p_enable); + session->toggle_profiler("multiplayer:replication", p_enable); +} + +void MultiplayerEditorDebugger::setup_session(int p_session_id) { + Ref<EditorDebuggerSession> session = get_session(p_session_id); + ERR_FAIL_COND(session.is_null()); + EditorNetworkProfiler *profiler = memnew(EditorNetworkProfiler); + profiler->connect("enable_profiling", callable_mp(this, &MultiplayerEditorDebugger::_profiler_activate).bind(p_session_id)); + profiler->connect("open_request", callable_mp(this, &MultiplayerEditorDebugger::_open_request)); + profiler->set_name(TTR("Network Profiler")); + session->add_session_tab(profiler); + profilers[p_session_id] = profiler; +} + +/// MultiplayerEditorPlugin + +MultiplayerEditorPlugin::MultiplayerEditorPlugin() { + repl_editor = memnew(ReplicationEditor); + button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Replication"), repl_editor); + button->hide(); + repl_editor->get_pin()->connect("pressed", callable_mp(this, &MultiplayerEditorPlugin::_pinned)); + debugger.instantiate(); + debugger->connect("open_request", callable_mp(this, &MultiplayerEditorPlugin::_open_request)); +} + +void MultiplayerEditorPlugin::_open_request(const String &p_path) { + get_editor_interface()->open_scene_from_path(p_path); +} + +void MultiplayerEditorPlugin::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + get_tree()->connect("node_removed", callable_mp(this, &MultiplayerEditorPlugin::_node_removed)); + add_debugger_plugin(debugger); + } break; + case NOTIFICATION_EXIT_TREE: { + remove_debugger_plugin(debugger); + } + } +} + +void MultiplayerEditorPlugin::_node_removed(Node *p_node) { + if (p_node && p_node == repl_editor->get_current()) { + repl_editor->edit(nullptr); + if (repl_editor->is_visible_in_tree()) { + EditorNode::get_singleton()->hide_bottom_panel(); + } + button->hide(); + repl_editor->get_pin()->set_pressed(false); + } +} + +void MultiplayerEditorPlugin::_pinned() { + if (!repl_editor->get_pin()->is_pressed()) { + if (repl_editor->is_visible_in_tree()) { + EditorNode::get_singleton()->hide_bottom_panel(); + } + button->hide(); + } +} + +void MultiplayerEditorPlugin::edit(Object *p_object) { + repl_editor->edit(Object::cast_to<MultiplayerSynchronizer>(p_object)); +} + +bool MultiplayerEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("MultiplayerSynchronizer"); +} + +void MultiplayerEditorPlugin::make_visible(bool p_visible) { + if (p_visible) { + button->show(); + EditorNode::get_singleton()->make_bottom_panel_item_visible(repl_editor); + } else if (!repl_editor->get_pin()->is_pressed()) { + if (repl_editor->is_visible_in_tree()) { + EditorNode::get_singleton()->hide_bottom_panel(); + } + button->hide(); + } +} diff --git a/modules/multiplayer/editor/multiplayer_editor_plugin.h b/modules/multiplayer/editor/multiplayer_editor_plugin.h new file mode 100644 index 0000000000..f29a70e897 --- /dev/null +++ b/modules/multiplayer/editor/multiplayer_editor_plugin.h @@ -0,0 +1,85 @@ +/*************************************************************************/ +/* multiplayer_editor_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef MULTIPLAYER_EDITOR_PLUGIN_H +#define MULTIPLAYER_EDITOR_PLUGIN_H + +#include "editor/editor_plugin.h" + +#include "editor/plugins/editor_debugger_plugin.h" + +class EditorNetworkProfiler; +class MultiplayerEditorDebugger : public EditorDebuggerPlugin { + GDCLASS(MultiplayerEditorDebugger, EditorDebuggerPlugin); + +private: + HashMap<int, EditorNetworkProfiler *> profilers; + + void _open_request(const String &p_path); + void _profiler_activate(bool p_enable, int p_session_id); + +protected: + static void _bind_methods(); + +public: + virtual bool has_capture(const String &p_capture) const override; + virtual bool capture(const String &p_message, const Array &p_data, int p_index) override; + virtual void setup_session(int p_session_id) override; + + MultiplayerEditorDebugger() {} +}; + +class ReplicationEditor; + +class MultiplayerEditorPlugin : public EditorPlugin { + GDCLASS(MultiplayerEditorPlugin, EditorPlugin); + +private: + Button *button = nullptr; + ReplicationEditor *repl_editor = nullptr; + Ref<MultiplayerEditorDebugger> debugger; + + void _open_request(const String &p_path); + void _node_removed(Node *p_node); + + void _pinned(); + +protected: + void _notification(int p_what); + +public: + virtual void edit(Object *p_object) override; + virtual bool handles(Object *p_object) const override; + virtual void make_visible(bool p_visible) override; + + MultiplayerEditorPlugin(); +}; + +#endif // MULTIPLAYER_EDITOR_PLUGIN_H diff --git a/modules/multiplayer/editor/replication_editor_plugin.cpp b/modules/multiplayer/editor/replication_editor.cpp index aee5f5b483..ccde9fed22 100644 --- a/modules/multiplayer/editor/replication_editor_plugin.cpp +++ b/modules/multiplayer/editor/replication_editor.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* replication_editor_plugin.cpp */ +/* replication_editor.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,15 +28,17 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "replication_editor_plugin.h" +#include "replication_editor.h" + +#include "../multiplayer_synchronizer.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.h" #include "editor/inspector_dock.h" +#include "editor/property_selector.h" #include "editor/scene_tree_editor.h" -#include "modules/multiplayer/multiplayer_synchronizer.h" #include "scene/gui/dialogs.h" #include "scene/gui/separator.h" #include "scene/gui/tree.h" @@ -140,7 +142,7 @@ void ReplicationEditor::_add_sync_property(String p_path) { return; } - Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_singleton()->get_undo_redo(); + Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_singleton()->get_undo_redo(); undo_redo->create_action(TTR("Add property to synchronizer")); if (config.is_null()) { @@ -200,7 +202,7 @@ ReplicationEditor::ReplicationEditor() { add_pick_button = memnew(Button); add_pick_button->connect("pressed", callable_mp(this, &ReplicationEditor::_pick_new_property)); - add_pick_button->set_text(TTR("Add property to sync..")); + add_pick_button->set_text(TTR("Add property to sync...")); hb->add_child(add_pick_button); VSeparator *vs = memnew(VSeparator); vs->set_custom_minimum_size(Size2(30 * EDSCALE, 0)); @@ -492,62 +494,3 @@ void ReplicationEditor::_add_property(const NodePath &p_property, bool p_spawn, item->set_checked(2, p_sync); item->set_editable(2, true); } - -/// ReplicationEditorPlugin -ReplicationEditorPlugin::ReplicationEditorPlugin() { - repl_editor = memnew(ReplicationEditor); - button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Replication"), repl_editor); - button->hide(); - repl_editor->get_pin()->connect("pressed", callable_mp(this, &ReplicationEditorPlugin::_pinned)); -} - -ReplicationEditorPlugin::~ReplicationEditorPlugin() { -} - -void ReplicationEditorPlugin::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - get_tree()->connect("node_removed", callable_mp(this, &ReplicationEditorPlugin::_node_removed)); - } break; - } -} - -void ReplicationEditorPlugin::_node_removed(Node *p_node) { - if (p_node && p_node == repl_editor->get_current()) { - repl_editor->edit(nullptr); - if (repl_editor->is_visible_in_tree()) { - EditorNode::get_singleton()->hide_bottom_panel(); - } - button->hide(); - repl_editor->get_pin()->set_pressed(false); - } -} - -void ReplicationEditorPlugin::_pinned() { - if (!repl_editor->get_pin()->is_pressed()) { - if (repl_editor->is_visible_in_tree()) { - EditorNode::get_singleton()->hide_bottom_panel(); - } - button->hide(); - } -} - -void ReplicationEditorPlugin::edit(Object *p_object) { - repl_editor->edit(Object::cast_to<MultiplayerSynchronizer>(p_object)); -} - -bool ReplicationEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("MultiplayerSynchronizer"); -} - -void ReplicationEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - button->show(); - EditorNode::get_singleton()->make_bottom_panel_item_visible(repl_editor); - } else if (!repl_editor->get_pin()->is_pressed()) { - if (repl_editor->is_visible_in_tree()) { - EditorNode::get_singleton()->hide_bottom_panel(); - } - button->hide(); - } -} diff --git a/modules/multiplayer/editor/replication_editor_plugin.h b/modules/multiplayer/editor/replication_editor.h index e60e49cc25..8a48e8dbe7 100644 --- a/modules/multiplayer/editor/replication_editor_plugin.h +++ b/modules/multiplayer/editor/replication_editor.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* replication_editor_plugin.h */ +/* replication_editor.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,21 +28,21 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef REPLICATION_EDITOR_PLUGIN_H -#define REPLICATION_EDITOR_PLUGIN_H +#ifndef REPLICATION_EDITOR_H +#define REPLICATION_EDITOR_H #include "editor/editor_plugin.h" - -#include "editor/editor_spin_slider.h" -#include "editor/property_selector.h" - -#include "../scene_replication_config.h" +#include "modules/multiplayer/scene_replication_config.h" +#include "scene/gui/box_container.h" class ConfirmationDialog; class MultiplayerSynchronizer; -class SceneTreeDialog; +class AcceptDialog; +class LineEdit; class Tree; class TreeItem; +class PropertySelector; +class SceneTreeDialog; class ReplicationEditor : public VBoxContainer { GDCLASS(ReplicationEditor, VBoxContainer); @@ -105,27 +105,4 @@ public: ~ReplicationEditor() {} }; -class ReplicationEditorPlugin : public EditorPlugin { - GDCLASS(ReplicationEditorPlugin, EditorPlugin); - -private: - Button *button = nullptr; - ReplicationEditor *repl_editor = nullptr; - - void _node_removed(Node *p_node); - - void _pinned(); - -protected: - void _notification(int p_what); - -public: - virtual void edit(Object *p_object) override; - virtual bool handles(Object *p_object) const override; - virtual void make_visible(bool p_visible) override; - - ReplicationEditorPlugin(); - ~ReplicationEditorPlugin(); -}; - -#endif // REPLICATION_EDITOR_PLUGIN_H +#endif // REPLICATION_EDITOR_H diff --git a/modules/multiplayer/multiplayer_debugger.cpp b/modules/multiplayer/multiplayer_debugger.cpp new file mode 100644 index 0000000000..9086ee6ec9 --- /dev/null +++ b/modules/multiplayer/multiplayer_debugger.cpp @@ -0,0 +1,333 @@ +/*************************************************************************/ +/* multiplayer_debugger.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "multiplayer_debugger.h" + +#include "multiplayer_synchronizer.h" +#include "scene_replication_config.h" + +#include "core/debugger/engine_debugger.h" +#include "scene/main/node.h" + +List<Ref<EngineProfiler>> multiplayer_profilers; + +void MultiplayerDebugger::initialize() { + Ref<BandwidthProfiler> bandwidth; + bandwidth.instantiate(); + bandwidth->bind("multiplayer:bandwidth"); + multiplayer_profilers.push_back(bandwidth); + + Ref<RPCProfiler> rpc_profiler; + rpc_profiler.instantiate(); + rpc_profiler->bind("multiplayer:rpc"); + multiplayer_profilers.push_back(rpc_profiler); + + Ref<ReplicationProfiler> replication_profiler; + replication_profiler.instantiate(); + replication_profiler->bind("multiplayer:replication"); + multiplayer_profilers.push_back(replication_profiler); + + EngineDebugger::register_message_capture("multiplayer", EngineDebugger::Capture(nullptr, &_capture)); +} + +void MultiplayerDebugger::deinitialize() { + multiplayer_profilers.clear(); +} + +Error MultiplayerDebugger::_capture(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured) { + if (p_msg == "cache") { + Array out; + for (int i = 0; i < p_args.size(); i++) { + ObjectID id = p_args[i].operator ObjectID(); + Object *obj = ObjectDB::get_instance(id); + ERR_CONTINUE(!obj); + if (Object::cast_to<SceneReplicationConfig>(obj)) { + out.push_back(id); + out.push_back(obj->get_class()); + out.push_back(((SceneReplicationConfig *)obj)->get_path()); + } else if (Object::cast_to<Node>(obj)) { + out.push_back(id); + out.push_back(obj->get_class()); + out.push_back(String(((Node *)obj)->get_path())); + } else { + ERR_FAIL_V(FAILED); + } + } + EngineDebugger::get_singleton()->send_message("multiplayer:cache", out); + return OK; + } + ERR_FAIL_V(FAILED); +} + +// BandwidthProfiler + +int MultiplayerDebugger::BandwidthProfiler::bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer) { + ERR_FAIL_COND_V(p_buffer.size() == 0, 0); + int total_bandwidth = 0; + + uint64_t timestamp = OS::get_singleton()->get_ticks_msec(); + uint64_t final_timestamp = timestamp - 1000; + + int i = (p_pointer + p_buffer.size() - 1) % p_buffer.size(); + + while (i != p_pointer && p_buffer[i].packet_size > 0) { + if (p_buffer[i].timestamp < final_timestamp) { + return total_bandwidth; + } + total_bandwidth += p_buffer[i].packet_size; + i = (i + p_buffer.size() - 1) % p_buffer.size(); + } + + ERR_FAIL_COND_V_MSG(i == p_pointer, total_bandwidth, "Reached the end of the bandwidth profiler buffer, values might be inaccurate."); + return total_bandwidth; +} + +void MultiplayerDebugger::BandwidthProfiler::toggle(bool p_enable, const Array &p_opts) { + if (!p_enable) { + bandwidth_in.clear(); + bandwidth_out.clear(); + } else { + bandwidth_in_ptr = 0; + bandwidth_in.resize(16384); // ~128kB + for (int i = 0; i < bandwidth_in.size(); ++i) { + bandwidth_in.write[i].packet_size = -1; + } + bandwidth_out_ptr = 0; + bandwidth_out.resize(16384); // ~128kB + for (int i = 0; i < bandwidth_out.size(); ++i) { + bandwidth_out.write[i].packet_size = -1; + } + } +} + +void MultiplayerDebugger::BandwidthProfiler::add(const Array &p_data) { + ERR_FAIL_COND(p_data.size() < 3); + const String inout = p_data[0]; + int time = p_data[1]; + int size = p_data[2]; + if (inout == "in") { + bandwidth_in.write[bandwidth_in_ptr].timestamp = time; + bandwidth_in.write[bandwidth_in_ptr].packet_size = size; + bandwidth_in_ptr = (bandwidth_in_ptr + 1) % bandwidth_in.size(); + } else if (inout == "out") { + bandwidth_out.write[bandwidth_out_ptr].timestamp = time; + bandwidth_out.write[bandwidth_out_ptr].packet_size = size; + bandwidth_out_ptr = (bandwidth_out_ptr + 1) % bandwidth_out.size(); + } +} + +void MultiplayerDebugger::BandwidthProfiler::tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) { + uint64_t pt = OS::get_singleton()->get_ticks_msec(); + if (pt - last_bandwidth_time > 200) { + last_bandwidth_time = pt; + int incoming_bandwidth = bandwidth_usage(bandwidth_in, bandwidth_in_ptr); + int outgoing_bandwidth = bandwidth_usage(bandwidth_out, bandwidth_out_ptr); + + Array arr; + arr.push_back(incoming_bandwidth); + arr.push_back(outgoing_bandwidth); + EngineDebugger::get_singleton()->send_message("multiplayer:bandwidth", arr); + } +} + +// RPCProfiler + +Array MultiplayerDebugger::RPCFrame::serialize() { + Array arr; + arr.push_back(infos.size() * 6); + for (int i = 0; i < infos.size(); ++i) { + arr.push_back(uint64_t(infos[i].node)); + arr.push_back(infos[i].node_path); + arr.push_back(infos[i].incoming_rpc); + arr.push_back(infos[i].incoming_size); + arr.push_back(infos[i].outgoing_rpc); + arr.push_back(infos[i].outgoing_size); + } + return arr; +} + +bool MultiplayerDebugger::RPCFrame::deserialize(const Array &p_arr) { + ERR_FAIL_COND_V(p_arr.size() < 1, false); + uint32_t size = p_arr[0]; + ERR_FAIL_COND_V(size % 6, false); + ERR_FAIL_COND_V((uint32_t)p_arr.size() != size + 1, false); + infos.resize(size / 6); + int idx = 1; + for (uint32_t i = 0; i < size / 6; i++) { + infos.write[i].node = uint64_t(p_arr[idx]); + infos.write[i].node_path = p_arr[idx + 1]; + infos.write[i].incoming_rpc = p_arr[idx + 2]; + infos.write[i].incoming_size = p_arr[idx + 3]; + infos.write[i].outgoing_rpc = p_arr[idx + 4]; + infos.write[i].outgoing_size = p_arr[idx + 5]; + idx += 6; + } + return true; +} + +void MultiplayerDebugger::RPCProfiler::init_node(const ObjectID p_node) { + if (rpc_node_data.has(p_node)) { + return; + } + rpc_node_data.insert(p_node, RPCNodeInfo()); + rpc_node_data[p_node].node = p_node; + rpc_node_data[p_node].node_path = Object::cast_to<Node>(ObjectDB::get_instance(p_node))->get_path(); +} + +void MultiplayerDebugger::RPCProfiler::toggle(bool p_enable, const Array &p_opts) { + rpc_node_data.clear(); +} + +void MultiplayerDebugger::RPCProfiler::add(const Array &p_data) { + ERR_FAIL_COND(p_data.size() != 3); + const String what = p_data[0]; + const ObjectID id = p_data[1]; + const int size = p_data[2]; + init_node(id); + RPCNodeInfo &info = rpc_node_data[id]; + if (what == "rpc_in") { + info.incoming_rpc++; + info.incoming_size += size; + } else if (what == "rpc_out") { + info.outgoing_rpc++; + info.outgoing_size += size; + } +} + +void MultiplayerDebugger::RPCProfiler::tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) { + uint64_t pt = OS::get_singleton()->get_ticks_msec(); + if (pt - last_profile_time > 100) { + last_profile_time = pt; + RPCFrame frame; + for (const KeyValue<ObjectID, RPCNodeInfo> &E : rpc_node_data) { + frame.infos.push_back(E.value); + } + rpc_node_data.clear(); + EngineDebugger::get_singleton()->send_message("multiplayer:rpc", frame.serialize()); + } +} + +// ReplicationProfiler + +MultiplayerDebugger::SyncInfo::SyncInfo(MultiplayerSynchronizer *p_sync) { + ERR_FAIL_COND(!p_sync); + synchronizer = p_sync->get_instance_id(); + if (p_sync->get_replication_config().is_valid()) { + config = p_sync->get_replication_config()->get_instance_id(); + } + if (p_sync->get_root_node()) { + root_node = p_sync->get_root_node()->get_instance_id(); + } +} + +void MultiplayerDebugger::SyncInfo::write_to_array(Array &r_arr) const { + r_arr.push_back(synchronizer); + r_arr.push_back(config); + r_arr.push_back(root_node); + r_arr.push_back(incoming_syncs); + r_arr.push_back(incoming_size); + r_arr.push_back(outgoing_syncs); + r_arr.push_back(outgoing_size); +} + +bool MultiplayerDebugger::SyncInfo::read_from_array(const Array &p_arr, int p_offset) { + ERR_FAIL_COND_V(p_arr.size() - p_offset < 7, false); + synchronizer = int64_t(p_arr[p_offset]); + config = int64_t(p_arr[p_offset + 1]); + root_node = int64_t(p_arr[p_offset + 2]); + incoming_syncs = p_arr[p_offset + 3]; + incoming_size = p_arr[p_offset + 4]; + outgoing_syncs = p_arr[p_offset + 5]; + outgoing_size = p_arr[p_offset + 6]; + return true; +} + +Array MultiplayerDebugger::ReplicationFrame::serialize() { + Array arr; + arr.push_back(infos.size() * 7); + for (const KeyValue<ObjectID, SyncInfo> &E : infos) { + E.value.write_to_array(arr); + } + return arr; +} + +bool MultiplayerDebugger::ReplicationFrame::deserialize(const Array &p_arr) { + ERR_FAIL_COND_V(p_arr.size() < 1, false); + uint32_t size = p_arr[0]; + ERR_FAIL_COND_V(size % 7, false); + ERR_FAIL_COND_V((uint32_t)p_arr.size() != size + 1, false); + int idx = 1; + for (uint32_t i = 0; i < size / 7; i++) { + SyncInfo info; + if (!info.read_from_array(p_arr, idx)) { + return false; + } + infos[info.synchronizer] = info; + idx += 7; + } + return true; +} + +void MultiplayerDebugger::ReplicationProfiler::toggle(bool p_enable, const Array &p_opts) { + sync_data.clear(); +} + +void MultiplayerDebugger::ReplicationProfiler::add(const Array &p_data) { + ERR_FAIL_COND(p_data.size() != 3); + const String what = p_data[0]; + const ObjectID id = p_data[1]; + const uint64_t size = p_data[2]; + MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(ObjectDB::get_instance(id)); + ERR_FAIL_COND(!sync); + if (!sync_data.has(id)) { + sync_data[id] = SyncInfo(sync); + } + SyncInfo &info = sync_data[id]; + if (what == "sync_in") { + info.incoming_syncs++; + info.incoming_size += size; + } else if (what == "sync_out") { + info.outgoing_syncs++; + info.outgoing_size += size; + } +} + +void MultiplayerDebugger::ReplicationProfiler::tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) { + uint64_t pt = OS::get_singleton()->get_ticks_msec(); + if (pt - last_profile_time > 100) { + last_profile_time = pt; + ReplicationFrame frame; + for (const KeyValue<ObjectID, SyncInfo> &E : sync_data) { + frame.infos[E.key] = E.value; + } + sync_data.clear(); + EngineDebugger::get_singleton()->send_message("multiplayer:syncs", frame.serialize()); + } +} diff --git a/modules/multiplayer/multiplayer_debugger.h b/modules/multiplayer/multiplayer_debugger.h new file mode 100644 index 0000000000..f5c092f0f9 --- /dev/null +++ b/modules/multiplayer/multiplayer_debugger.h @@ -0,0 +1,134 @@ +/*************************************************************************/ +/* multiplayer_debugger.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef MULTIPLAYER_DEBUGGER_H +#define MULTIPLAYER_DEBUGGER_H + +#include "core/debugger/engine_profiler.h" + +#include "core/os/os.h" + +class MultiplayerSynchronizer; + +class MultiplayerDebugger { +public: + struct RPCNodeInfo { + ObjectID node; + String node_path; + int incoming_rpc = 0; + int incoming_size = 0; + int outgoing_rpc = 0; + int outgoing_size = 0; + }; + + struct RPCFrame { + Vector<RPCNodeInfo> infos; + + Array serialize(); + bool deserialize(const Array &p_arr); + }; + + struct SyncInfo { + ObjectID synchronizer; + ObjectID config; + ObjectID root_node; + int incoming_syncs = 0; + int incoming_size = 0; + int outgoing_syncs = 0; + int outgoing_size = 0; + + void write_to_array(Array &r_arr) const; + bool read_from_array(const Array &p_arr, int p_offset); + + SyncInfo() {} + SyncInfo(MultiplayerSynchronizer *p_sync); + }; + + struct ReplicationFrame { + HashMap<ObjectID, SyncInfo> infos; + + Array serialize(); + bool deserialize(const Array &p_arr); + }; + +private: + class BandwidthProfiler : public EngineProfiler { + protected: + struct BandwidthFrame { + uint32_t timestamp; + int packet_size; + }; + + int bandwidth_in_ptr = 0; + Vector<BandwidthFrame> bandwidth_in; + int bandwidth_out_ptr = 0; + Vector<BandwidthFrame> bandwidth_out; + uint64_t last_bandwidth_time = 0; + + int bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer); + + public: + void toggle(bool p_enable, const Array &p_opts); + void add(const Array &p_data); + void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time); + }; + + class RPCProfiler : public EngineProfiler { + private: + HashMap<ObjectID, RPCNodeInfo> rpc_node_data; + uint64_t last_profile_time = 0; + + void init_node(const ObjectID p_node); + + public: + void toggle(bool p_enable, const Array &p_opts); + void add(const Array &p_data); + void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time); + }; + + class ReplicationProfiler : public EngineProfiler { + private: + HashMap<ObjectID, SyncInfo> sync_data; + uint64_t last_profile_time = 0; + + public: + void toggle(bool p_enable, const Array &p_opts); + void add(const Array &p_data); + void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time); + }; + + static Error _capture(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured); + +public: + static void initialize(); + static void deinitialize(); +}; + +#endif // MULTIPLAYER_DEBUGGER_H diff --git a/modules/multiplayer/register_types.cpp b/modules/multiplayer/register_types.cpp index a2c524da80..ca85a46f31 100644 --- a/modules/multiplayer/register_types.cpp +++ b/modules/multiplayer/register_types.cpp @@ -36,9 +36,10 @@ #include "scene_replication_interface.h" #include "scene_rpc_interface.h" +#include "multiplayer_debugger.h" + #ifdef TOOLS_ENABLED -#include "editor/editor_plugin.h" -#include "editor/replication_editor_plugin.h" +#include "editor/multiplayer_editor_plugin.h" #endif void initialize_multiplayer_module(ModuleInitializationLevel p_level) { @@ -46,15 +47,18 @@ void initialize_multiplayer_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(SceneReplicationConfig); GDREGISTER_CLASS(MultiplayerSpawner); GDREGISTER_CLASS(MultiplayerSynchronizer); + GDREGISTER_CLASS(OfflineMultiplayerPeer); GDREGISTER_CLASS(SceneMultiplayer); MultiplayerAPI::set_default_interface("SceneMultiplayer"); + MultiplayerDebugger::initialize(); } #ifdef TOOLS_ENABLED if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) { - EditorPlugins::add_by_type<ReplicationEditorPlugin>(); + EditorPlugins::add_by_type<MultiplayerEditorPlugin>(); } #endif } void uninitialize_multiplayer_module(ModuleInitializationLevel p_level) { + MultiplayerDebugger::deinitialize(); } diff --git a/modules/multiplayer/scene_cache_interface.cpp b/modules/multiplayer/scene_cache_interface.cpp index 7df9b95b30..f0da4f9dfc 100644 --- a/modules/multiplayer/scene_cache_interface.cpp +++ b/modules/multiplayer/scene_cache_interface.cpp @@ -99,10 +99,6 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac Ref<MultiplayerPeer> multiplayer_peer = multiplayer->get_multiplayer_peer(); ERR_FAIL_COND(multiplayer_peer.is_null()); -#ifdef DEBUG_ENABLED - multiplayer->profile_bandwidth("out", packet.size()); -#endif - multiplayer_peer->set_transfer_channel(0); multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); multiplayer->send_command(p_from, packet.ptr(), packet.size()); @@ -155,10 +151,6 @@ Error SceneCacheInterface::_send_confirm_path(Node *p_node, NodePath p_path, Pat Ref<MultiplayerPeer> multiplayer_peer = multiplayer->get_multiplayer_peer(); ERR_FAIL_COND_V(multiplayer_peer.is_null(), ERR_BUG); -#ifdef DEBUG_ENABLED - multiplayer->profile_bandwidth("out", packet.size() * p_peers.size()); -#endif - Error err = OK; for (int peer_id : p_peers) { multiplayer_peer->set_transfer_channel(0); diff --git a/modules/multiplayer/scene_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp index db7c5037cd..5042a0502d 100644 --- a/modules/multiplayer/scene_multiplayer.cpp +++ b/modules/multiplayer/scene_multiplayer.cpp @@ -40,13 +40,13 @@ #endif #ifdef DEBUG_ENABLED -void SceneMultiplayer::profile_bandwidth(const String &p_inout, int p_size) { - if (EngineDebugger::is_profiling("multiplayer")) { +_FORCE_INLINE_ void SceneMultiplayer::_profile_bandwidth(const String &p_what, int p_value) { + if (EngineDebugger::is_profiling("multiplayer:bandwidth")) { Array values; - values.push_back(p_inout); + values.push_back(p_what); values.push_back(OS::get_singleton()->get_ticks_msec()); - values.push_back(p_size); - EngineDebugger::profiler_add_frame_data("multiplayer", values); + values.push_back(p_value); + EngineDebugger::profiler_add_frame_data("multiplayer:bandwidth", values); } } #endif @@ -91,6 +91,10 @@ Error SceneMultiplayer::poll() { Error err = multiplayer_peer->get_packet(&packet, len); ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Error getting packet! %d", err)); +#ifdef DEBUG_ENABLED + _profile_bandwidth("in", len); +#endif + if (pending_peers.has(sender)) { if (pending_peers[sender].local) { // If the auth is over, admit the peer at the first packet. @@ -220,10 +224,6 @@ void SceneMultiplayer::_process_packet(int p_from, const uint8_t *p_packet, int ERR_FAIL_COND_MSG(root_path.is_empty(), "Multiplayer root was not initialized. If you are using custom multiplayer, remember to set the root path via SceneMultiplayer.set_root_path before using it."); ERR_FAIL_COND_MSG(p_packet_len < 1, "Invalid packet received. Size too small."); -#ifdef DEBUG_ENABLED - profile_bandwidth("in", p_packet_len); -#endif - // Extract the `packet_type` from the LSB three bits: uint8_t packet_type = p_packet[0] & CMD_MASK; @@ -258,6 +258,13 @@ void SceneMultiplayer::_process_packet(int p_from, const uint8_t *p_packet, int } } +#ifdef DEBUG_ENABLED +_FORCE_INLINE_ Error SceneMultiplayer::_send(const uint8_t *p_packet, int p_packet_len) { + _profile_bandwidth("out", p_packet_len); + return multiplayer_peer->put_packet(p_packet, p_packet_len); +} +#endif + Error SceneMultiplayer::send_command(int p_to, const uint8_t *p_packet, int p_packet_len) { if (server_relay && get_unique_id() != 1 && p_to != 1 && multiplayer_peer->is_server_relay_supported()) { // Send relay packet. @@ -268,19 +275,19 @@ Error SceneMultiplayer::send_command(int p_to, const uint8_t *p_packet, int p_pa relay_buffer->put_data(p_packet, p_packet_len); multiplayer_peer->set_target_peer(1); const Vector<uint8_t> data = relay_buffer->get_data_array(); - return multiplayer_peer->put_packet(data.ptr(), relay_buffer->get_position()); + return _send(data.ptr(), relay_buffer->get_position()); } if (p_to > 0) { ERR_FAIL_COND_V(!connected_peers.has(p_to), ERR_BUG); multiplayer_peer->set_target_peer(p_to); - return multiplayer_peer->put_packet(p_packet, p_packet_len); + return _send(p_packet, p_packet_len); } else { for (const int &pid : connected_peers) { if (p_to && pid == -p_to) { continue; } multiplayer_peer->set_target_peer(pid); - multiplayer_peer->put_packet(p_packet, p_packet_len); + _send(p_packet, p_packet_len); } return OK; } @@ -319,7 +326,7 @@ void SceneMultiplayer::_process_sys(int p_from, const uint8_t *p_packet, int p_p multiplayer_peer->set_transfer_channel(p_channel); if (peer > 0) { multiplayer_peer->set_target_peer(peer); - multiplayer_peer->put_packet(data.ptr(), relay_buffer->get_position()); + _send(data.ptr(), relay_buffer->get_position()); } else { for (const int &P : connected_peers) { // Not to sender, nor excluded. @@ -327,7 +334,7 @@ void SceneMultiplayer::_process_sys(int p_from, const uint8_t *p_packet, int p_p continue; } multiplayer_peer->set_target_peer(P); - multiplayer_peer->put_packet(data.ptr(), relay_buffer->get_position()); + _send(data.ptr(), relay_buffer->get_position()); } } if (peer == 0 || peer == -1) { @@ -373,11 +380,11 @@ void SceneMultiplayer::_admit_peer(int p_id) { // Send new peer to already connected. encode_uint32(p_id, &buf[2]); multiplayer_peer->set_target_peer(P); - multiplayer_peer->put_packet(buf, sizeof(buf)); + _send(buf, sizeof(buf)); // Send already connected to new peer. encode_uint32(P, &buf[2]); multiplayer_peer->set_target_peer(p_id); - multiplayer_peer->put_packet(buf, sizeof(buf)); + _send(buf, sizeof(buf)); } } @@ -412,7 +419,7 @@ void SceneMultiplayer::_del_peer(int p_id) { continue; } multiplayer_peer->set_target_peer(P); - multiplayer_peer->put_packet(buf, sizeof(buf)); + _send(buf, sizeof(buf)); } } @@ -468,7 +475,7 @@ Error SceneMultiplayer::send_auth(int p_to, Vector<uint8_t> p_data) { multiplayer_peer->set_target_peer(p_to); multiplayer_peer->set_transfer_channel(0); multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE); - return multiplayer_peer->put_packet(packet_cache.ptr(), p_data.size() + 2); + return _send(packet_cache.ptr(), p_data.size() + 2); } Error SceneMultiplayer::complete_auth(int p_peer) { @@ -478,7 +485,7 @@ Error SceneMultiplayer::complete_auth(int p_peer) { pending_peers[p_peer].local = true; // Notify the remote peer that the authentication has completed. uint8_t buf[2] = { NETWORK_COMMAND_SYS, SYS_COMMAND_AUTH }; - Error err = multiplayer_peer->put_packet(buf, 2); + Error err = _send(buf, 2); // The remote peer already reported the authentication as completed, so admit the peer. // May generate new packets, so it must happen after sending confirmation. if (pending_peers[p_peer].remote) { @@ -654,6 +661,7 @@ SceneMultiplayer::SceneMultiplayer() { replicator = Ref<SceneReplicationInterface>(memnew(SceneReplicationInterface(this))); rpc = Ref<SceneRPCInterface>(memnew(SceneRPCInterface(this))); cache = Ref<SceneCacheInterface>(memnew(SceneCacheInterface(this))); + set_multiplayer_peer(Ref<OfflineMultiplayerPeer>(memnew(OfflineMultiplayerPeer))); } SceneMultiplayer::~SceneMultiplayer() { diff --git a/modules/multiplayer/scene_multiplayer.h b/modules/multiplayer/scene_multiplayer.h index b0ecc48f8c..1a8de11f3f 100644 --- a/modules/multiplayer/scene_multiplayer.h +++ b/modules/multiplayer/scene_multiplayer.h @@ -37,6 +37,31 @@ #include "scene_replication_interface.h" #include "scene_rpc_interface.h" +class OfflineMultiplayerPeer : public MultiplayerPeer { + GDCLASS(OfflineMultiplayerPeer, MultiplayerPeer); + +public: + virtual int get_available_packet_count() const override { return 0; } + virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override { + *r_buffer = nullptr; + r_buffer_size = 0; + return OK; + } + virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override { return OK; } + virtual int get_max_packet_size() const override { return 0; } + + virtual void set_target_peer(int p_peer_id) override {} + virtual int get_packet_peer() const override { return 0; } + virtual TransferMode get_packet_mode() const override { return TRANSFER_MODE_RELIABLE; }; + virtual int get_packet_channel() const override { return 0; } + virtual void disconnect_peer(int p_peer, bool p_force = false) override {} + virtual bool is_server() const override { return true; } + virtual void poll() override {} + virtual void close() override {} + virtual int get_unique_id() const override { return TARGET_PEER_SERVER; } + virtual ConnectionStatus get_connection_status() const override { return CONNECTION_CONNECTED; }; +}; + class SceneMultiplayer : public MultiplayerAPI { GDCLASS(SceneMultiplayer, MultiplayerAPI); @@ -103,6 +128,15 @@ private: Ref<SceneReplicationInterface> replicator; Ref<SceneRPCInterface> rpc; +#ifdef DEBUG_ENABLED + _FORCE_INLINE_ void _profile_bandwidth(const String &p_what, int p_value); + _FORCE_INLINE_ Error _send(const uint8_t *p_packet, int p_packet_len); // Also profiles. +#else + _FORCE_INLINE_ Error _send(const uint8_t *p_packet, int p_packet_len) { + return multiplayer_peer->put_packet(p_packet, p_packet_len); + } +#endif + protected: static void _bind_methods(); @@ -162,10 +196,7 @@ public: bool is_server_relay_enabled() const; Ref<SceneCacheInterface> get_path_cache() { return cache; } - -#ifdef DEBUG_ENABLED - void profile_bandwidth(const String &p_inout, int p_size); -#endif + Ref<SceneReplicationInterface> get_replicator() { return replicator; } SceneMultiplayer(); ~SceneMultiplayer(); diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp index f1bab7327a..7d9437936a 100644 --- a/modules/multiplayer/scene_replication_interface.cpp +++ b/modules/multiplayer/scene_replication_interface.cpp @@ -32,6 +32,7 @@ #include "scene_multiplayer.h" +#include "core/debugger/engine_debugger.h" #include "core/io/marshalls.h" #include "scene/main/node.h" #include "scene/scene_string_names.h" @@ -40,6 +41,18 @@ if (packet_cache.size() < m_amount) \ packet_cache.resize(m_amount); +#ifdef DEBUG_ENABLED +_FORCE_INLINE_ void SceneReplicationInterface::_profile_node_data(const String &p_what, ObjectID p_id, int p_size) { + if (EngineDebugger::is_profiling("multiplayer:replication")) { + Array values; + values.push_back(p_what); + values.push_back(p_id); + values.push_back(p_size); + EngineDebugger::profiler_add_frame_data("multiplayer:replication", values); + } +} +#endif + SceneReplicationInterface::TrackedNode &SceneReplicationInterface::_track(const ObjectID &p_id) { if (!tracked_nodes.has(p_id)) { tracked_nodes[p_id] = TrackedNode(p_id); @@ -244,15 +257,54 @@ void SceneReplicationInterface::_visibility_changed(int p_peer, ObjectID p_sid) Node *node = sync->get_root_node(); ERR_FAIL_COND(!node); // Bug. const ObjectID oid = node->get_instance_id(); - if (spawned_nodes.has(oid)) { + if (spawned_nodes.has(oid) && p_peer != multiplayer->get_unique_id()) { _update_spawn_visibility(p_peer, oid); } _update_sync_visibility(p_peer, sync); } +bool SceneReplicationInterface::is_rpc_visible(const ObjectID &p_oid, int p_peer) const { + if (!tracked_nodes.has(p_oid)) { + return true; // Untracked nodes are always visible to RPCs. + } + ERR_FAIL_COND_V(p_peer < 0, false); + const TrackedNode &tnode = tracked_nodes[p_oid]; + if (tnode.synchronizers.is_empty()) { + return true; // No synchronizers means no visibility restrictions. + } + if (tnode.remote_peer && uint32_t(p_peer) == tnode.remote_peer) { + return true; // RPCs on spawned nodes are always visible to spawner. + } else if (spawned_nodes.has(p_oid)) { + // It's a spwaned node we control, this can be fast + if (p_peer) { + return peers_info.has(p_peer) && peers_info[p_peer].spawn_nodes.has(p_oid); + } else { + for (const KeyValue<int, PeerInfo> &E : peers_info) { + if (!E.value.spawn_nodes.has(p_oid)) { + return false; // Not public. + } + } + return true; // All peers have this node. + } + } else { + // Cycle object synchronizers to check visibility. + for (const ObjectID &sid : tnode.synchronizers) { + MultiplayerSynchronizer *sync = get_id_as<MultiplayerSynchronizer>(sid); + ERR_CONTINUE(!sync); + // RPC visibility is composed using OR when multiple synchronizers are present. + // Note that we don't really care about authority here which may lead to unexpected + // results when using multiple synchronizers to control the same node. + if (sync->is_visible_to(p_peer)) { + return true; + } + } + return false; // Not visible. + } +} + Error SceneReplicationInterface::_update_sync_visibility(int p_peer, MultiplayerSynchronizer *p_sync) { ERR_FAIL_COND_V(!p_sync, ERR_BUG); - if (!multiplayer->has_multiplayer_peer() || !p_sync->is_multiplayer_authority()) { + if (!multiplayer->has_multiplayer_peer() || !p_sync->is_multiplayer_authority() || p_peer == multiplayer->get_unique_id()) { return OK; } @@ -362,13 +414,8 @@ Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const Obje Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable) { ERR_FAIL_COND_V(!p_buffer || p_size < 1, ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(!multiplayer, ERR_UNCONFIGURED); ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED); -#ifdef DEBUG_ENABLED - multiplayer->profile_bandwidth("out", p_size); -#endif - Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); peer->set_transfer_channel(0); peer->set_transfer_mode(p_reliable ? MultiplayerPeer::TRANSFER_MODE_RELIABLE : MultiplayerPeer::TRANSFER_MODE_UNRELIABLE); @@ -640,6 +687,9 @@ void SceneReplicationInterface::_send_sync(int p_peer, const HashSet<ObjectID> p MultiplayerAPI::encode_and_compress_variants(varp.ptrw(), varp.size(), &ptr[ofs], size); ofs += size; } +#ifdef DEBUG_ENABLED + _profile_node_data("sync_out", oid, size); +#endif } if (ofs > 3) { // Got some left over to send. @@ -687,6 +737,9 @@ Error SceneReplicationInterface::on_sync_receive(int p_from, const uint8_t *p_bu err = MultiplayerSynchronizer::set_state(props, node, vars); ERR_FAIL_COND_V(err, err); ofs += size; +#ifdef DEBUG_ENABLED + _profile_node_data("sync_in", sync->get_instance_id(), size); +#endif } return OK; } diff --git a/modules/multiplayer/scene_replication_interface.h b/modules/multiplayer/scene_replication_interface.h index c8bd96eb87..30d58f7129 100644 --- a/modules/multiplayer/scene_replication_interface.h +++ b/modules/multiplayer/scene_replication_interface.h @@ -105,6 +105,10 @@ private: return p_id.is_valid() ? Object::cast_to<T>(ObjectDB::get_instance(p_id)) : nullptr; } +#ifdef DEBUG_ENABLED + _FORCE_INLINE_ void _profile_node_data(const String &p_what, ObjectID p_id, int p_size); +#endif + public: static void make_default(); @@ -121,6 +125,8 @@ public: Error on_despawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len); Error on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len); + bool is_rpc_visible(const ObjectID &p_oid, int p_peer) const; + SceneReplicationInterface(SceneMultiplayer *p_multiplayer) { multiplayer = p_multiplayer; } diff --git a/modules/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp index acc113c901..a7e29dfcc7 100644 --- a/modules/multiplayer/scene_rpc_interface.cpp +++ b/modules/multiplayer/scene_rpc_interface.cpp @@ -52,16 +52,15 @@ #define BYTE_ONLY_OR_NO_ARGS_FLAG (1 << BYTE_ONLY_OR_NO_ARGS_SHIFT) #ifdef DEBUG_ENABLED -_FORCE_INLINE_ void SceneRPCInterface::_profile_node_data(const String &p_what, ObjectID p_id) { - if (EngineDebugger::is_profiling("rpc")) { +_FORCE_INLINE_ void SceneRPCInterface::_profile_node_data(const String &p_what, ObjectID p_id, int p_size) { + if (EngineDebugger::is_profiling("multiplayer:rpc")) { Array values; - values.push_back(p_id); values.push_back(p_what); - EngineDebugger::profiler_add_frame_data("rpc", values); + values.push_back(p_id); + values.push_back(p_size); + EngineDebugger::profiler_add_frame_data("multiplayer:rpc", values); } } -#else -_FORCE_INLINE_ void SceneRPCInterface::_profile_node_data(const String &p_what, ObjectID p_id) {} #endif // Returns the packet size stripping the node path added when the node is not yet cached. @@ -83,7 +82,7 @@ void SceneRPCInterface::_parse_rpc_config(const Variant &p_config, bool p_for_no Array names = config.keys(); names.sort(); // Ensure ID order for (int i = 0; i < names.size(); i++) { - ERR_CONTINUE(names[i].get_type() != Variant::STRING); + ERR_CONTINUE(names[i].get_type() != Variant::STRING && names[i].get_type() != Variant::STRING_NAME); String name = names[i].operator String(); ERR_CONTINUE(config[name].get_type() != Variant::DICTIONARY); ERR_CONTINUE(!config[name].operator Dictionary().has("rpc_mode")); @@ -277,7 +276,7 @@ void SceneRPCInterface::_process_rpc(Node *p_node, const uint16_t p_rpc_method_i argp.resize(argc); #ifdef DEBUG_ENABLED - _profile_node_data("rpc_in", p_node->get_instance_id()); + _profile_node_data("rpc_in", p_node->get_instance_id(), p_packet_len); #endif int out; @@ -296,7 +295,7 @@ void SceneRPCInterface::_process_rpc(Node *p_node, const uint16_t p_rpc_method_i } } -void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount) { +void SceneRPCInterface::_send_rpc(Node *p_node, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount) { Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer(); ERR_FAIL_COND_MSG(peer.is_null(), "Attempt to call RPC without active multiplayer peer."); @@ -312,12 +311,35 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con ERR_FAIL_MSG("Attempt to call RPC with unknown peer ID: " + itos(p_to) + "."); } - // See if all peers have cached path (if so, call can be fast). - int psc_id; - const bool has_all_peers = multiplayer->get_path_cache()->send_object_cache(p_from, p_to, psc_id); + // See if all peers have cached path (if so, call can be fast) while building the RPC target list. + HashSet<int> targets; + Ref<SceneCacheInterface> cache = multiplayer->get_path_cache(); + int psc_id = -1; + bool has_all_peers = true; + const ObjectID oid = p_node->get_instance_id(); + if (p_to > 0) { + ERR_FAIL_COND_MSG(!multiplayer->get_replicator()->is_rpc_visible(oid, p_to), "Attempt to call an RPC to a peer that cannot see this node. Peer ID: " + itos(p_to)); + targets.insert(p_to); + has_all_peers = cache->send_object_cache(p_node, p_to, psc_id); + } else { + bool restricted = !multiplayer->get_replicator()->is_rpc_visible(oid, 0); + for (const int &P : multiplayer->get_connected_peers()) { + if (p_to < 0 && P == -p_to) { + continue; // Excluded peer. + } + if (restricted && !multiplayer->get_replicator()->is_rpc_visible(oid, P)) { + continue; // Not visible to this peer. + } + targets.insert(P); + bool has_peer = cache->send_object_cache(p_node, P, psc_id); + has_all_peers = has_all_peers && has_peer; + } + } + if (targets.is_empty()) { + return; // No one in sight. + } // Create base packet, lots of hardcode because it must be tight. - int ofs = 0; #define MAKE_ROOM(m_amount) \ @@ -399,20 +421,21 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con ERR_FAIL_COND(node_id_compression > 3); ERR_FAIL_COND(name_id_compression > 1); - // We can now set the meta - packet_cache.write[0] = command_type + (node_id_compression << NODE_ID_COMPRESSION_SHIFT) + (name_id_compression << NAME_ID_COMPRESSION_SHIFT) + (byte_only_or_no_args ? BYTE_ONLY_OR_NO_ARGS_FLAG : 0); - #ifdef DEBUG_ENABLED - multiplayer->profile_bandwidth("out", ofs); + _profile_node_data("rpc_out", p_node->get_instance_id(), ofs); #endif + // We can now set the meta + packet_cache.write[0] = command_type + (node_id_compression << NODE_ID_COMPRESSION_SHIFT) + (name_id_compression << NAME_ID_COMPRESSION_SHIFT) + (byte_only_or_no_args ? BYTE_ONLY_OR_NO_ARGS_FLAG : 0); + // Take chance and set transfer mode, since all send methods will use it. peer->set_transfer_channel(p_config.channel); peer->set_transfer_mode(p_config.transfer_mode); if (has_all_peers) { - // They all have verified paths, so send fast. - multiplayer->send_command(p_to, packet_cache.ptr(), ofs); + for (const int P : targets) { + multiplayer->send_command(P, packet_cache.ptr(), ofs); + } } else { // Unreachable because the node ID is never compressed if the peers doesn't know it. CRASH_COND(node_id_compression != NETWORK_NODE_ID_COMPRESSION_32); @@ -420,23 +443,15 @@ void SceneRPCInterface::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, con // Not all verified path, so send one by one. // Append path at the end, since we will need it for some packets. - NodePath from_path = multiplayer->get_root_path().rel_path_to(p_from->get_path()); + NodePath from_path = multiplayer->get_root_path().rel_path_to(p_node->get_path()); CharString pname = String(from_path).utf8(); int path_len = encode_cstring(pname.get_data(), nullptr); MAKE_ROOM(ofs + path_len); encode_cstring(pname.get_data(), &(packet_cache.write[ofs])); - for (const int &P : multiplayer->get_connected_peers()) { - if (p_to < 0 && P == -p_to) { - continue; // Continue, excluded. - } - - if (p_to > 0 && P != p_to) { - continue; // Continue, not for this peer. - } - + // Not all verified path, so check which needs the longer packet. + for (const int P : targets) { bool confirmed = multiplayer->get_path_cache()->is_cache_confirmed(from_path, P); - if (confirmed) { // This one confirmed path, so use id. encode_uint32(psc_id, &(packet_cache.write[1])); @@ -477,10 +492,6 @@ Error SceneRPCInterface::rpcp(Object *p_obj, int p_peer_id, const StringName &p_ } if (p_peer_id != caller_id) { -#ifdef DEBUG_ENABLED - _profile_node_data("rpc_out", node->get_instance_id()); -#endif - _send_rpc(node, p_peer_id, rpc_id, config, p_method, p_arg, p_argcount); } diff --git a/modules/multiplayer/scene_rpc_interface.h b/modules/multiplayer/scene_rpc_interface.h index aa9be525a2..800293714c 100644 --- a/modules/multiplayer/scene_rpc_interface.h +++ b/modules/multiplayer/scene_rpc_interface.h @@ -81,8 +81,11 @@ private: HashMap<ObjectID, RPCConfigCache> rpc_cache; +#ifdef DEBUG_ENABLED + _FORCE_INLINE_ void _profile_node_data(const String &p_what, ObjectID p_id, int p_size); +#endif + protected: - _FORCE_INLINE_ void _profile_node_data(const String &p_what, ObjectID p_id); void _process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset); void _send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount); diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.h b/modules/navigation/editor/navigation_mesh_editor_plugin.h index bc9e4185b7..b7bde98131 100644 --- a/modules/navigation/editor/navigation_mesh_editor_plugin.h +++ b/modules/navigation/editor/navigation_mesh_editor_plugin.h @@ -35,6 +35,8 @@ #include "editor/editor_plugin.h" +class AcceptDialog; +class HBoxContainer; class NavigationRegion3D; class NavigationMeshEditor : public Control { diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp index 8ca73a3adb..0e40e5a4af 100644 --- a/modules/navigation/godot_navigation_server.cpp +++ b/modules/navigation/godot_navigation_server.cpp @@ -383,6 +383,20 @@ real_t GodotNavigationServer::region_get_travel_cost(RID p_region) const { return region->get_travel_cost(); } +COMMAND_2(region_set_owner_id, RID, p_region, ObjectID, p_owner_id) { + NavRegion *region = region_owner.get_or_null(p_region); + ERR_FAIL_COND(region == nullptr); + + region->set_owner_id(p_owner_id); +} + +ObjectID GodotNavigationServer::region_get_owner_id(RID p_region) const { + const NavRegion *region = region_owner.get_or_null(p_region); + ERR_FAIL_COND_V(region == nullptr, ObjectID()); + + return region->get_owner_id(); +} + bool GodotNavigationServer::region_owns_point(RID p_region, const Vector3 &p_point) const { const NavRegion *region = region_owner.get_or_null(p_region); ERR_FAIL_COND_V(region == nullptr, false); @@ -570,6 +584,20 @@ real_t GodotNavigationServer::link_get_travel_cost(const RID p_link) const { return link->get_travel_cost(); } +COMMAND_2(link_set_owner_id, RID, p_link, ObjectID, p_owner_id) { + NavLink *link = link_owner.get_or_null(p_link); + ERR_FAIL_COND(link == nullptr); + + link->set_owner_id(p_owner_id); +} + +ObjectID GodotNavigationServer::link_get_owner_id(RID p_link) const { + const NavLink *link = link_owner.get_or_null(p_link); + ERR_FAIL_COND_V(link == nullptr, ObjectID()); + + return link->get_owner_id(); +} + RID GodotNavigationServer::agent_create() const { GodotNavigationServer *mut_this = const_cast<GodotNavigationServer *>(this); MutexLock lock(mut_this->operations_mutex); diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h index ab5e722d35..08ad545b37 100644 --- a/modules/navigation/godot_navigation_server.h +++ b/modules/navigation/godot_navigation_server.h @@ -125,6 +125,9 @@ public: COMMAND_2(region_set_travel_cost, RID, p_region, real_t, p_travel_cost); virtual real_t region_get_travel_cost(RID p_region) const override; + COMMAND_2(region_set_owner_id, RID, p_region, ObjectID, p_owner_id); + virtual ObjectID region_get_owner_id(RID p_region) const override; + virtual bool region_owns_point(RID p_region, const Vector3 &p_point) const override; COMMAND_2(region_set_map, RID, p_region, RID, p_map); @@ -153,6 +156,8 @@ public: virtual real_t link_get_enter_cost(RID p_link) const override; COMMAND_2(link_set_travel_cost, RID, p_link, real_t, p_travel_cost); virtual real_t link_get_travel_cost(RID p_link) const override; + COMMAND_2(link_set_owner_id, RID, p_link, ObjectID, p_owner_id); + virtual ObjectID link_get_owner_id(RID p_link) const override; virtual RID agent_create() const override; COMMAND_2(agent_set_map, RID, p_agent, RID, p_map); diff --git a/modules/navigation/nav_base.h b/modules/navigation/nav_base.h index 6dfaaf9af4..f5d2880d36 100644 --- a/modules/navigation/nav_base.h +++ b/modules/navigation/nav_base.h @@ -41,6 +41,7 @@ protected: uint32_t navigation_layers = 1; float enter_cost = 0.0; float travel_cost = 1.0; + ObjectID owner_id; public: void set_navigation_layers(uint32_t p_navigation_layers) { navigation_layers = p_navigation_layers; } @@ -51,6 +52,9 @@ public: void set_travel_cost(float p_travel_cost) { travel_cost = MAX(p_travel_cost, 0.0); } float get_travel_cost() const { return travel_cost; } + + void set_owner_id(ObjectID p_owner_id) { owner_id = p_owner_id; } + ObjectID get_owner_id() const { return owner_id; } }; #endif // NAV_BASE_H diff --git a/modules/navigation/nav_link.h b/modules/navigation/nav_link.h index 8d57f076c0..8f51a63951 100644 --- a/modules/navigation/nav_link.h +++ b/modules/navigation/nav_link.h @@ -37,8 +37,8 @@ class NavLink : public NavBase { NavMap *map = nullptr; bool bidirectional = true; - Vector3 start_location = Vector3(); - Vector3 end_location = Vector3(); + Vector3 start_location; + Vector3 end_location; bool link_dirty = true; diff --git a/modules/noise/editor/noise_editor_plugin.cpp b/modules/noise/editor/noise_editor_plugin.cpp index e8e73e4fd9..a8e376e142 100644 --- a/modules/noise/editor/noise_editor_plugin.cpp +++ b/modules/noise/editor/noise_editor_plugin.cpp @@ -32,7 +32,10 @@ #ifdef TOOLS_ENABLED +#include "editor/editor_inspector.h" #include "editor/editor_scale.h" +#include "scene/gui/button.h" +#include "scene/gui/texture_rect.h" #include "modules/noise/noise.h" #include "modules/noise/noise_texture_2d.h" diff --git a/modules/openxr/action_map/openxr_action.cpp b/modules/openxr/action_map/openxr_action.cpp index 0fb4f0773f..7e02f0374d 100644 --- a/modules/openxr/action_map/openxr_action.cpp +++ b/modules/openxr/action_map/openxr_action.cpp @@ -42,7 +42,7 @@ void OpenXRAction::_bind_methods() { ClassDB::bind_method(D_METHOD("set_toplevel_paths", "toplevel_paths"), &OpenXRAction::set_toplevel_paths); ClassDB::bind_method(D_METHOD("get_toplevel_paths"), &OpenXRAction::get_toplevel_paths); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "toplevel_paths", PROPERTY_HINT_ARRAY_TYPE, "STRING"), "set_toplevel_paths", "get_toplevel_paths"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "toplevel_paths"), "set_toplevel_paths", "get_toplevel_paths"); BIND_ENUM_CONSTANT(OPENXR_ACTION_BOOL); BIND_ENUM_CONSTANT(OPENXR_ACTION_FLOAT); diff --git a/modules/openxr/action_map/openxr_interaction_profile.cpp b/modules/openxr/action_map/openxr_interaction_profile.cpp index 99d7a17acf..abb714c3bb 100644 --- a/modules/openxr/action_map/openxr_interaction_profile.cpp +++ b/modules/openxr/action_map/openxr_interaction_profile.cpp @@ -38,7 +38,7 @@ void OpenXRIPBinding::_bind_methods() { ClassDB::bind_method(D_METHOD("get_path_count"), &OpenXRIPBinding::get_path_count); ClassDB::bind_method(D_METHOD("set_paths", "paths"), &OpenXRIPBinding::set_paths); ClassDB::bind_method(D_METHOD("get_paths"), &OpenXRIPBinding::get_paths); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "paths", PROPERTY_HINT_ARRAY_TYPE, "STRING"), "set_paths", "get_paths"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "paths"), "set_paths", "get_paths"); ClassDB::bind_method(D_METHOD("has_path", "path"), &OpenXRIPBinding::has_path); ClassDB::bind_method(D_METHOD("add_path", "path"), &OpenXRIPBinding::add_path); diff --git a/modules/openxr/config.py b/modules/openxr/config.py index 279168cc59..e503f12739 100644 --- a/modules/openxr/config.py +++ b/modules/openxr/config.py @@ -1,6 +1,6 @@ def can_build(env, platform): if platform in ("linuxbsd", "windows", "android"): - return env["openxr"] + return env["openxr"] and not env["disable_3d"] else: # not supported on these platforms return False diff --git a/modules/openxr/extensions/openxr_android_extension.cpp b/modules/openxr/extensions/openxr_android_extension.cpp index 8f6d5c28db..753fc5fa89 100644 --- a/modules/openxr/extensions/openxr_android_extension.cpp +++ b/modules/openxr/extensions/openxr_android_extension.cpp @@ -47,10 +47,15 @@ OpenXRAndroidExtension *OpenXRAndroidExtension::get_singleton() { OpenXRAndroidExtension::OpenXRAndroidExtension(OpenXRAPI *p_openxr_api) : OpenXRExtensionWrapper(p_openxr_api) { singleton = this; - request_extensions[XR_KHR_ANDROID_THREAD_SETTINGS_EXTENSION_NAME] = nullptr; // must be available + request_extensions[XR_KHR_LOADER_INIT_ANDROID_EXTENSION_NAME] = &loader_init_extension_available; + request_extensions[XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME] = &create_instance_extension_available; } void OpenXRAndroidExtension::on_before_instance_created() { + if (!loader_init_extension_available) { + print_line("OpenXR: XR_KHR_loader_init_android is not reported as available - trying to initialize anyway..."); + } + EXT_INIT_XR_FUNC(xrInitializeLoaderKHR); JNIEnv *env = get_jni_env(); @@ -68,6 +73,29 @@ void OpenXRAndroidExtension::on_before_instance_created() { ERR_FAIL_COND_MSG(XR_FAILED(result), "Failed to call xrInitializeLoaderKHR"); } +// We're keeping the Android create info struct here to avoid including openxr_platform.h in a header, which would break other extensions. +// This is reasonably safe as the struct is only used during intialization and the extension is a singleton. +static XrInstanceCreateInfoAndroidKHR instance_create_info; + +void *OpenXRAndroidExtension::set_instance_create_info_and_get_next_pointer(void *p_next_pointer) { + if (!create_instance_extension_available) { + return nullptr; + } + + JNIEnv *env = get_jni_env(); + JavaVM *vm; + env->GetJavaVM(&vm); + jobject activity_object = env->NewGlobalRef(static_cast<OS_Android *>(OS::get_singleton())->get_godot_java()->get_activity()); + + instance_create_info = { + .type = XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR, + .next = p_next_pointer, + .applicationVM = vm, + .applicationActivity = activity_object + }; + return &instance_create_info; +} + OpenXRAndroidExtension::~OpenXRAndroidExtension() { singleton = nullptr; } diff --git a/modules/openxr/extensions/openxr_android_extension.h b/modules/openxr/extensions/openxr_android_extension.h index eda7022064..087b634756 100644 --- a/modules/openxr/extensions/openxr_android_extension.h +++ b/modules/openxr/extensions/openxr_android_extension.h @@ -41,12 +41,16 @@ public: OpenXRAndroidExtension(OpenXRAPI *p_openxr_api); virtual void on_before_instance_created() override; + virtual void *set_instance_create_info_and_get_next_pointer(void *p_next_pointer) override; virtual ~OpenXRAndroidExtension() override; private: static OpenXRAndroidExtension *singleton; + bool loader_init_extension_available = false; + bool create_instance_extension_available = false; + // Initialize the loader EXT_PROTO_XRRESULT_FUNC1(xrInitializeLoaderKHR, (const XrLoaderInitInfoBaseHeaderKHR *), loaderInitInfo) }; diff --git a/modules/openxr/extensions/openxr_extension_wrapper.h b/modules/openxr/extensions/openxr_extension_wrapper.h index c417c90d11..77b52ab355 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper.h +++ b/modules/openxr/extensions/openxr_extension_wrapper.h @@ -65,6 +65,7 @@ public: virtual void *set_system_properties_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; } virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; } virtual void *set_swapchain_create_info_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; } + virtual void *set_instance_create_info_and_get_next_pointer(void *p_next_pointer) { return p_next_pointer; } virtual void on_before_instance_created() {} virtual void on_instance_created(const XrInstance p_instance) {} diff --git a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp index 4d996e6283..29208efb20 100644 --- a/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp +++ b/modules/openxr/extensions/openxr_htc_vive_tracker_extension.cpp @@ -91,8 +91,6 @@ bool OpenXRHTCViveTrackerExtension::is_path_supported(const String &p_path) { return available; } else if (p_path == "/user/vive_tracker_htcx/role/chest") { return available; - } else if (p_path == "/user/vive_tracker_htcx/role/chest") { - return available; } else if (p_path == "/user/vive_tracker_htcx/role/camera") { return available; } else if (p_path == "/user/vive_tracker_htcx/role/keyboard") { diff --git a/modules/openxr/extensions/openxr_opengl_extension.cpp b/modules/openxr/extensions/openxr_opengl_extension.cpp index ee69144123..3b7c130149 100644 --- a/modules/openxr/extensions/openxr_opengl_extension.cpp +++ b/modules/openxr/extensions/openxr_opengl_extension.cpp @@ -136,9 +136,9 @@ void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_nex graphics_binding_gl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR; graphics_binding_gl.next = p_next_pointer; - graphics_binding_gl.display = eglGetCurrentDisplay(); + graphics_binding_gl.display = (void *)display_server->window_get_native_handle(DisplayServer::DISPLAY_HANDLE); graphics_binding_gl.config = (EGLConfig)0; // https://github.com/KhronosGroup/OpenXR-SDK-Source/blob/master/src/tests/hello_xr/graphicsplugin_opengles.cpp#L122 - graphics_binding_gl.context = eglGetCurrentContext(); + graphics_binding_gl.context = (void *)display_server->window_get_native_handle(DisplayServer::OPENGL_CONTEXT); #else graphics_binding_gl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR; graphics_binding_gl.next = p_next_pointer; @@ -160,16 +160,8 @@ void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_nex } void OpenXROpenGLExtension::get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) { -#ifdef WIN32 - p_usable_swap_chains.push_back(GL_SRGB8_ALPHA8); - p_usable_swap_chains.push_back(GL_RGBA8); -#elif ANDROID_ENABLED p_usable_swap_chains.push_back(GL_SRGB8_ALPHA8); p_usable_swap_chains.push_back(GL_RGBA8); -#else - p_usable_swap_chains.push_back(GL_SRGB8_ALPHA8_EXT); - p_usable_swap_chains.push_back(GL_RGBA8_EXT); -#endif } void OpenXROpenGLExtension::get_usable_depth_formats(Vector<int64_t> &p_usable_depth_formats) { @@ -294,59 +286,7 @@ void OpenXROpenGLExtension::cleanup_swapchain_graphics_data(void **p_swapchain_g String OpenXROpenGLExtension::get_swapchain_format_name(int64_t p_swapchain_format) const { // These are somewhat different per platform, will need to weed some stuff out... switch (p_swapchain_format) { -#ifdef WIN32 - // using definitions from GLAD - ENUM_TO_STRING_CASE(GL_R8_SNORM) - ENUM_TO_STRING_CASE(GL_RG8_SNORM) - ENUM_TO_STRING_CASE(GL_RGB8_SNORM) - ENUM_TO_STRING_CASE(GL_RGBA8_SNORM) - ENUM_TO_STRING_CASE(GL_R16_SNORM) - ENUM_TO_STRING_CASE(GL_RG16_SNORM) - ENUM_TO_STRING_CASE(GL_RGB16_SNORM) - ENUM_TO_STRING_CASE(GL_RGBA16_SNORM) - ENUM_TO_STRING_CASE(GL_RGB4) - ENUM_TO_STRING_CASE(GL_RGB5) - ENUM_TO_STRING_CASE(GL_RGB8) - ENUM_TO_STRING_CASE(GL_RGB10) - ENUM_TO_STRING_CASE(GL_RGB12) - ENUM_TO_STRING_CASE(GL_RGB16) - ENUM_TO_STRING_CASE(GL_RGBA2) - ENUM_TO_STRING_CASE(GL_RGBA4) - ENUM_TO_STRING_CASE(GL_RGB5_A1) - ENUM_TO_STRING_CASE(GL_RGBA8) - ENUM_TO_STRING_CASE(GL_RGB10_A2) - ENUM_TO_STRING_CASE(GL_RGBA12) - ENUM_TO_STRING_CASE(GL_RGBA16) - ENUM_TO_STRING_CASE(GL_RGBA32F) - ENUM_TO_STRING_CASE(GL_RGB32F) - ENUM_TO_STRING_CASE(GL_RGBA16F) - ENUM_TO_STRING_CASE(GL_RGB16F) - ENUM_TO_STRING_CASE(GL_RGBA32UI) - ENUM_TO_STRING_CASE(GL_RGB32UI) - ENUM_TO_STRING_CASE(GL_RGBA16UI) - ENUM_TO_STRING_CASE(GL_RGB16UI) - ENUM_TO_STRING_CASE(GL_RGBA8UI) - ENUM_TO_STRING_CASE(GL_RGB8UI) - ENUM_TO_STRING_CASE(GL_RGBA32I) - ENUM_TO_STRING_CASE(GL_RGB32I) - ENUM_TO_STRING_CASE(GL_RGBA16I) - ENUM_TO_STRING_CASE(GL_RGB16I) - ENUM_TO_STRING_CASE(GL_RGBA8I) - ENUM_TO_STRING_CASE(GL_RGB8I) - ENUM_TO_STRING_CASE(GL_RGB10_A2UI) - ENUM_TO_STRING_CASE(GL_SRGB) - ENUM_TO_STRING_CASE(GL_SRGB8) - ENUM_TO_STRING_CASE(GL_SRGB_ALPHA) - ENUM_TO_STRING_CASE(GL_SRGB8_ALPHA8) - ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT16) - ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT24) - ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT32) - ENUM_TO_STRING_CASE(GL_DEPTH24_STENCIL8) - ENUM_TO_STRING_CASE(GL_R11F_G11F_B10F) - ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT32F) - ENUM_TO_STRING_CASE(GL_DEPTH32F_STENCIL8) - -#elif ANDROID_ENABLED +#ifdef ANDROID_ENABLED // using definitions from GLES3/gl3.h ENUM_TO_STRING_CASE(GL_RGBA4) @@ -418,44 +358,56 @@ String OpenXROpenGLExtension::get_swapchain_format_name(int64_t p_swapchain_form ENUM_TO_STRING_CASE(GL_DEPTH24_STENCIL8) #else - // using definitions from GL/gl.h - ENUM_TO_STRING_CASE(GL_ALPHA4_EXT) - ENUM_TO_STRING_CASE(GL_ALPHA8_EXT) - ENUM_TO_STRING_CASE(GL_ALPHA12_EXT) - ENUM_TO_STRING_CASE(GL_ALPHA16_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE4_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE8_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE12_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE16_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE4_ALPHA4_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE6_ALPHA2_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE8_ALPHA8_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE12_ALPHA4_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE12_ALPHA12_EXT) - ENUM_TO_STRING_CASE(GL_LUMINANCE16_ALPHA16_EXT) - ENUM_TO_STRING_CASE(GL_INTENSITY_EXT) - ENUM_TO_STRING_CASE(GL_INTENSITY4_EXT) - ENUM_TO_STRING_CASE(GL_INTENSITY8_EXT) - ENUM_TO_STRING_CASE(GL_INTENSITY12_EXT) - ENUM_TO_STRING_CASE(GL_INTENSITY16_EXT) - ENUM_TO_STRING_CASE(GL_RGB2_EXT) - ENUM_TO_STRING_CASE(GL_RGB4_EXT) - ENUM_TO_STRING_CASE(GL_RGB5_EXT) - ENUM_TO_STRING_CASE(GL_RGB8_EXT) - ENUM_TO_STRING_CASE(GL_RGB10_EXT) - ENUM_TO_STRING_CASE(GL_RGB12_EXT) - ENUM_TO_STRING_CASE(GL_RGB16_EXT) - ENUM_TO_STRING_CASE(GL_RGBA2_EXT) - ENUM_TO_STRING_CASE(GL_RGBA4_EXT) - ENUM_TO_STRING_CASE(GL_RGB5_A1_EXT) - ENUM_TO_STRING_CASE(GL_RGBA8_EXT) - ENUM_TO_STRING_CASE(GL_RGB10_A2_EXT) - ENUM_TO_STRING_CASE(GL_RGBA12_EXT) - ENUM_TO_STRING_CASE(GL_RGBA16_EXT) - ENUM_TO_STRING_CASE(GL_SRGB_EXT) - ENUM_TO_STRING_CASE(GL_SRGB8_EXT) - ENUM_TO_STRING_CASE(GL_SRGB_ALPHA_EXT) - ENUM_TO_STRING_CASE(GL_SRGB8_ALPHA8_EXT) + // using definitions from GLAD + ENUM_TO_STRING_CASE(GL_R8_SNORM) + ENUM_TO_STRING_CASE(GL_RG8_SNORM) + ENUM_TO_STRING_CASE(GL_RGB8_SNORM) + ENUM_TO_STRING_CASE(GL_RGBA8_SNORM) + ENUM_TO_STRING_CASE(GL_R16_SNORM) + ENUM_TO_STRING_CASE(GL_RG16_SNORM) + ENUM_TO_STRING_CASE(GL_RGB16_SNORM) + ENUM_TO_STRING_CASE(GL_RGBA16_SNORM) + ENUM_TO_STRING_CASE(GL_RGB4) + ENUM_TO_STRING_CASE(GL_RGB5) + ENUM_TO_STRING_CASE(GL_RGB8) + ENUM_TO_STRING_CASE(GL_RGB10) + ENUM_TO_STRING_CASE(GL_RGB12) + ENUM_TO_STRING_CASE(GL_RGB16) + ENUM_TO_STRING_CASE(GL_RGBA2) + ENUM_TO_STRING_CASE(GL_RGBA4) + ENUM_TO_STRING_CASE(GL_RGB5_A1) + ENUM_TO_STRING_CASE(GL_RGBA8) + ENUM_TO_STRING_CASE(GL_RGB10_A2) + ENUM_TO_STRING_CASE(GL_RGBA12) + ENUM_TO_STRING_CASE(GL_RGBA16) + ENUM_TO_STRING_CASE(GL_RGBA32F) + ENUM_TO_STRING_CASE(GL_RGB32F) + ENUM_TO_STRING_CASE(GL_RGBA16F) + ENUM_TO_STRING_CASE(GL_RGB16F) + ENUM_TO_STRING_CASE(GL_RGBA32UI) + ENUM_TO_STRING_CASE(GL_RGB32UI) + ENUM_TO_STRING_CASE(GL_RGBA16UI) + ENUM_TO_STRING_CASE(GL_RGB16UI) + ENUM_TO_STRING_CASE(GL_RGBA8UI) + ENUM_TO_STRING_CASE(GL_RGB8UI) + ENUM_TO_STRING_CASE(GL_RGBA32I) + ENUM_TO_STRING_CASE(GL_RGB32I) + ENUM_TO_STRING_CASE(GL_RGBA16I) + ENUM_TO_STRING_CASE(GL_RGB16I) + ENUM_TO_STRING_CASE(GL_RGBA8I) + ENUM_TO_STRING_CASE(GL_RGB8I) + ENUM_TO_STRING_CASE(GL_RGB10_A2UI) + ENUM_TO_STRING_CASE(GL_SRGB) + ENUM_TO_STRING_CASE(GL_SRGB8) + ENUM_TO_STRING_CASE(GL_SRGB_ALPHA) + ENUM_TO_STRING_CASE(GL_SRGB8_ALPHA8) + ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT16) + ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT24) + ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT32) + ENUM_TO_STRING_CASE(GL_DEPTH24_STENCIL8) + ENUM_TO_STRING_CASE(GL_R11F_G11F_B10F) + ENUM_TO_STRING_CASE(GL_DEPTH_COMPONENT32F) + ENUM_TO_STRING_CASE(GL_DEPTH32F_STENCIL8) #endif default: { return String("Swapchain format 0x") + String::num_int64(p_swapchain_format, 16); diff --git a/modules/openxr/extensions/openxr_opengl_extension.h b/modules/openxr/extensions/openxr_opengl_extension.h index b666653c8e..473c5157c0 100644 --- a/modules/openxr/extensions/openxr_opengl_extension.h +++ b/modules/openxr/extensions/openxr_opengl_extension.h @@ -59,9 +59,8 @@ #include OPENGL_INCLUDE_H #define GL_GLEXT_PROTOTYPES 1 #define GL3_PROTOTYPES 1 -#include <GL/gl.h> -#include <GL/glext.h> -#include <GL/glx.h> +#include "thirdparty/glad/glad/gl.h" +#include "thirdparty/glad/glad/glx.h" #include <X11/Xlib.h> #endif diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 88111afede..b652ca4617 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -51,7 +51,7 @@ #define XR_USE_GRAPHICS_API_VULKAN #endif #ifdef GLES3_ENABLED -#ifdef ANDROID +#ifdef ANDROID_ENABLED #define XR_USE_GRAPHICS_API_OPENGL_ES #include <EGL/egl.h> #include <EGL/eglext.h> @@ -59,14 +59,13 @@ #include <GLES3/gl3ext.h> #else #define XR_USE_GRAPHICS_API_OPENGL -#endif // ANDROID +#endif // ANDROID_ENABLED #ifdef X11_ENABLED #include OPENGL_INCLUDE_H #define GL_GLEXT_PROTOTYPES 1 #define GL3_PROTOTYPES 1 -#include <GL/gl.h> -#include <GL/glext.h> -#include <GL/glx.h> +#include "thirdparty/glad/glad/gl.h" +#include "thirdparty/glad/glad/glx.h" #include <X11/Xlib.h> #endif // X11_ENABLED #endif // GLES_ENABLED @@ -299,9 +298,17 @@ bool OpenXRAPI::create_instance() { XR_CURRENT_API_VERSION // apiVersion }; + void *next_pointer = nullptr; + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { + void *np = wrapper->set_instance_create_info_and_get_next_pointer(next_pointer); + if (np != nullptr) { + next_pointer = np; + } + } + XrInstanceCreateInfo instance_create_info = { XR_TYPE_INSTANCE_CREATE_INFO, // type - nullptr, // next + next_pointer, // next 0, // createFlags application_info, // applicationInfo 0, // enabledApiLayerCount, need to find out if we need support for this? @@ -733,9 +740,10 @@ bool OpenXRAPI::create_swapchains() { ERR_FAIL_NULL_V_MSG(projection_views, false, "OpenXR Couldn't allocate memory for projection views"); // We create our depth swapchain if: + // - we've enabled submitting depth buffer // - we support our depth layer extension // - we have our spacewarp extension (not yet implemented) - if (OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { + if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { // Build a vector with swapchain formats we want to use, from best fit to worst Vector<int64_t> usable_swapchain_formats; int64_t swapchain_format_to_use = 0; @@ -783,13 +791,13 @@ bool OpenXRAPI::create_swapchains() { projection_views[i].subImage.imageRect.extent.width = recommended_size.width; projection_views[i].subImage.imageRect.extent.height = recommended_size.height; - if (OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && depth_views) { + if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && depth_views) { projection_views[i].next = &depth_views[i]; depth_views[i].type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR; depth_views[i].next = nullptr; depth_views[i].subImage.swapchain = swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain; - depth_views[i].subImage.imageArrayIndex = 0; + depth_views[i].subImage.imageArrayIndex = i; depth_views[i].subImage.imageRect.offset.x = 0; depth_views[i].subImage.imageRect.offset.y = 0; depth_views[i].subImage.imageRect.extent.width = recommended_size.width; @@ -1059,6 +1067,30 @@ bool OpenXRAPI::on_state_exiting() { return true; } +void OpenXRAPI::set_form_factor(XrFormFactor p_form_factor) { + ERR_FAIL_COND(is_initialized()); + + form_factor = p_form_factor; +} + +void OpenXRAPI::set_view_configuration(XrViewConfigurationType p_view_configuration) { + ERR_FAIL_COND(is_initialized()); + + view_configuration = p_view_configuration; +} + +void OpenXRAPI::set_reference_space(XrReferenceSpaceType p_reference_space) { + ERR_FAIL_COND(is_initialized()); + + reference_space = p_reference_space; +} + +void OpenXRAPI::set_submit_depth_buffer(bool p_submit_depth_buffer) { + ERR_FAIL_COND(is_initialized()); + + submit_depth_buffer = p_submit_depth_buffer; +} + bool OpenXRAPI::is_initialized() { return (instance != XR_NULL_HANDLE); } @@ -1677,7 +1709,7 @@ RID OpenXRAPI::get_color_texture() { } RID OpenXRAPI::get_depth_texture() { - if (swapchains[OPENXR_SWAPCHAIN_DEPTH].image_acquired) { + if (submit_depth_buffer && swapchains[OPENXR_SWAPCHAIN_DEPTH].image_acquired) { return graphics_extension->get_texture(swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data, swapchains[OPENXR_SWAPCHAIN_DEPTH].image_index); } else { return RID(); @@ -1855,6 +1887,8 @@ OpenXRAPI::OpenXRAPI() { default: break; } + + submit_depth_buffer = GLOBAL_GET("xr/openxr/submit_depth_buffer"); } // reset a few things that can't be done in our class definition diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index 5dce749351..ac4bbff94c 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -104,6 +104,7 @@ private: XrViewConfigurationType view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; XrReferenceSpaceType reference_space = XR_REFERENCE_SPACE_TYPE_STAGE; // XrEnvironmentBlendMode environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; + bool submit_depth_buffer = false; // if set to true we submit depth buffers to OpenXR if a suitable extension is enabled. // state XrInstance instance = XR_NULL_HANDLE; @@ -312,6 +313,18 @@ public: void set_xr_interface(OpenXRInterface *p_xr_interface); void register_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper); + void set_form_factor(XrFormFactor p_form_factor); + XrFormFactor get_form_factor() const { return form_factor; } + + void set_view_configuration(XrViewConfigurationType p_view_configuration); + XrViewConfigurationType get_view_configuration() const { return view_configuration; } + + void set_reference_space(XrReferenceSpaceType p_reference_space); + XrReferenceSpaceType get_reference_space() const { return reference_space; } + + void set_submit_depth_buffer(bool p_submit_depth_buffer); + bool get_submit_depth_buffer() const { return submit_depth_buffer; } + bool is_initialized(); bool is_running(); bool initialize(const String &p_rendering_driver); diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index be6b7e4411..40190ab2f3 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -45,7 +45,7 @@ void OpenXRInterface::_bind_methods() { // Display refresh rate ClassDB::bind_method(D_METHOD("get_display_refresh_rate"), &OpenXRInterface::get_display_refresh_rate); ClassDB::bind_method(D_METHOD("set_display_refresh_rate", "refresh_rate"), &OpenXRInterface::set_display_refresh_rate); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "display_refresh_rate"), "set_display_refresh_rate", "get_display_refresh_rate"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "display_refresh_rate"), "set_display_refresh_rate", "get_display_refresh_rate"); ClassDB::bind_method(D_METHOD("get_available_display_refresh_rates"), &OpenXRInterface::get_available_display_refresh_rates); } @@ -666,6 +666,7 @@ Transform3D OpenXRInterface::get_camera_transform() { Transform3D OpenXRInterface::get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) { XRServer *xr_server = XRServer::get_singleton(); ERR_FAIL_NULL_V(xr_server, Transform3D()); + ERR_FAIL_UNSIGNED_INDEX_V_MSG(p_view, get_view_count(), Transform3D(), "View index outside bounds."); Transform3D t; if (openxr_api && openxr_api->get_view_transform(p_view, t)) { @@ -685,6 +686,7 @@ Transform3D OpenXRInterface::get_transform_for_view(uint32_t p_view, const Trans Projection OpenXRInterface::get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) { Projection cm; + ERR_FAIL_UNSIGNED_INDEX_V_MSG(p_view, get_view_count(), cm, "View index outside bounds."); if (openxr_api) { if (openxr_api->get_view_projection(p_view, p_z_near, p_z_far, cm)) { diff --git a/modules/raycast/SCsub b/modules/raycast/SCsub index 20b05816e1..51d75d45b0 100644 --- a/modules/raycast/SCsub +++ b/modules/raycast/SCsub @@ -67,7 +67,7 @@ if env["builtin_embree"]: env_raycast.AppendUnique(CPPDEFINES=["NDEBUG"]) # No assert() even in debug builds. if not env.msvc: - if env["arch"] == "x86_64": + if env["arch"] in ["x86_64", "x86_32"]: env_raycast.Append(CPPFLAGS=["-msse2", "-mxsave"]) if env["platform"] == "windows": @@ -87,10 +87,13 @@ if env["builtin_embree"]: env_thirdparty.disable_warnings() env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) - if env["arch"] == "arm64" or env.msvc: + if env["arch"] != "x86_64" or env.msvc: # Embree needs those, it will automatically use SSE2NEON in ARM env_thirdparty.Append(CPPDEFINES=["__SSE2__", "__SSE__"]) + if env["platform"] == "web": + env_thirdparty.Append(CPPFLAGS=["-msimd128"]) + if not env.msvc: env_thirdparty.Append( CPPFLAGS=[ diff --git a/modules/raycast/config.py b/modules/raycast/config.py index 833ad50018..f4243f01c5 100644 --- a/modules/raycast/config.py +++ b/modules/raycast/config.py @@ -1,9 +1,13 @@ def can_build(env, platform): - # Depends on Embree library, which only supports x86_64 and arm64. - if platform == "windows": - return env["arch"] == "x86_64" # TODO build for Windows on ARM - - return env["arch"] in ["x86_64", "arm64"] + # Supported architectures depend on the Embree library. + # No ARM32 support planned. + if env["arch"] == "arm32": + return False + # x86_32 only seems supported on Windows for now. + if env["arch"] == "x86_32" and platform != "windows": + return False + # The rest works, even wasm32! + return True def configure(env): diff --git a/modules/raycast/godot_update_embree.py b/modules/raycast/godot_update_embree.py index e31d88b741..527e02f855 100644 --- a/modules/raycast/godot_update_embree.py +++ b/modules/raycast/godot_update_embree.py @@ -1,5 +1,7 @@ import glob, os, shutil, subprocess, re +git_tag = "v3.13.5" + include_dirs = [ "common/tasking", "kernels/bvh", @@ -12,6 +14,7 @@ include_dirs = [ "common/lexers", "common/simd", "common/simd/arm", + "common/simd/wasm", "include/embree3", "kernels/subdiv", "kernels/geometry", @@ -76,6 +79,7 @@ if os.path.exists(dir_name): subprocess.run(["git", "clone", "https://github.com/embree/embree.git", "embree-tmp"]) os.chdir("embree-tmp") +subprocess.run(["git", "checkout", git_tag]) commit_hash = str(subprocess.check_output(["git", "rev-parse", "HEAD"], universal_newlines=True)).strip() @@ -94,8 +98,7 @@ for f in all_files: with open(os.path.join(dest_dir, "kernels/hash.h"), "w") as hash_file: hash_file.write( - f""" -// Copyright 2009-2020 Intel Corporation + f"""// Copyright 2009-2021 Intel Corporation // SPDX-License-Identifier: Apache-2.0 #define RTC_HASH "{commit_hash}" @@ -104,8 +107,7 @@ with open(os.path.join(dest_dir, "kernels/hash.h"), "w") as hash_file: with open(os.path.join(dest_dir, "kernels/config.h"), "w") as config_file: config_file.write( - """ -// Copyright 2009-2020 Intel Corporation + """// Copyright 2009-2021 Intel Corporation // SPDX-License-Identifier: Apache-2.0 /* #undef EMBREE_RAY_MASK */ @@ -126,6 +128,7 @@ with open(os.path.join(dest_dir, "kernels/config.h"), "w") as config_file: /* #undef EMBREE_COMPACT_POLYS */ #define EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR 2.0 +#define EMBREE_DISC_POINT_SELF_INTERSECTION_AVOIDANCE #if defined(EMBREE_GEOMETRY_TRIANGLE) #define IF_ENABLED_TRIS(x) x @@ -192,8 +195,7 @@ with open("CMakeLists.txt", "r") as cmake_file: with open(os.path.join(dest_dir, "include/embree3/rtcore_config.h"), "w") as config_file: config_file.write( - f""" -// Copyright 2009-2021 Intel Corporation + f"""// Copyright 2009-2021 Intel Corporation // SPDX-License-Identifier: Apache-2.0 #pragma once @@ -209,14 +211,16 @@ with open(os.path.join(dest_dir, "include/embree3/rtcore_config.h"), "w") as con #define EMBREE_MIN_WIDTH 0 #define RTC_MIN_WIDTH EMBREE_MIN_WIDTH -#define EMBREE_STATIC_LIB -/* #undef EMBREE_API_NAMESPACE */ +#if !defined(EMBREE_STATIC_LIB) +# define EMBREE_STATIC_LIB +#endif +/* #undef EMBREE_API_NAMESPACE*/ #if defined(EMBREE_API_NAMESPACE) # define RTC_NAMESPACE -# define RTC_NAMESPACE_BEGIN namespace {{ +# define RTC_NAMESPACE_BEGIN namespace {{ # define RTC_NAMESPACE_END }} -# define RTC_NAMESPACE_USE using namespace ; +# define RTC_NAMESPACE_USE using namespace; # define RTC_API_EXTERN_C # undef EMBREE_API_NAMESPACE #else diff --git a/modules/regex/regex.cpp b/modules/regex/regex.cpp index c808211d68..6f02d20c25 100644 --- a/modules/regex/regex.cpp +++ b/modules/regex/regex.cpp @@ -50,8 +50,7 @@ int RegExMatch::_find(const Variant &p_name) const { return -1; } return i; - - } else if (p_name.get_type() == Variant::STRING) { + } else if (p_name.get_type() == Variant::STRING || p_name.get_type() == Variant::STRING_NAME) { HashMap<String, int>::ConstIterator found = names.find((String)p_name); if (found) { return found->value; diff --git a/modules/svg/image_loader_svg.cpp b/modules/svg/image_loader_svg.cpp index b8c412a201..2dba4916a0 100644 --- a/modules/svg/image_loader_svg.cpp +++ b/modules/svg/image_loader_svg.cpp @@ -67,8 +67,8 @@ void ImageLoaderSVG::_replace_color_property(const HashMap<Color, Color> &p_colo } } -void ImageLoaderSVG::create_image_from_string(Ref<Image> p_image, String p_string, float p_scale, bool p_upsample, const HashMap<Color, Color> &p_color_map) { - ERR_FAIL_COND(Math::is_zero_approx(p_scale)); +Error ImageLoaderSVG::create_image_from_string(Ref<Image> p_image, String p_string, float p_scale, bool p_upsample, const HashMap<Color, Color> &p_color_map) { + ERR_FAIL_COND_V_MSG(Math::is_zero_approx(p_scale), ERR_INVALID_PARAMETER, "ImageLoaderSVG: Can't load SVG with a scale of 0."); if (p_color_map.size()) { _replace_color_property(p_color_map, "stop-color=\"", p_string); @@ -81,13 +81,23 @@ void ImageLoaderSVG::create_image_from_string(Ref<Image> p_image, String p_strin tvg::Result result = picture->load((const char *)bytes.ptr(), bytes.size(), "svg", true); if (result != tvg::Result::Success) { - return; + return ERR_INVALID_DATA; } float fw, fh; picture->size(&fw, &fh); - uint32_t width = MIN(round(fw * p_scale), 16 * 1024); - uint32_t height = MIN(round(fh * p_scale), 16 * 1024); + uint32_t width = round(fw * p_scale); + uint32_t height = round(fh * p_scale); + + const uint32_t max_dimension = 16384; + if (width > max_dimension || height > max_dimension) { + WARN_PRINT(vformat( + String::utf8("ImageLoaderSVG: Target canvas dimensions %d×%d (with scale %.2f) exceed the max supported dimensions %d×%d. The target canvas will be scaled down."), + width, height, p_scale, max_dimension, max_dimension)); + width = MIN(width, max_dimension); + height = MIN(height, max_dimension); + } + picture->size(width, height); std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen(); @@ -97,25 +107,25 @@ void ImageLoaderSVG::create_image_from_string(Ref<Image> p_image, String p_strin tvg::Result res = sw_canvas->target(buffer, width, width, height, tvg::SwCanvas::ARGB8888_STRAIGHT); if (res != tvg::Result::Success) { memfree(buffer); - ERR_FAIL_MSG("ImageLoaderSVG can't create image."); + ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't set target on ThorVG canvas."); } res = sw_canvas->push(std::move(picture)); if (res != tvg::Result::Success) { memfree(buffer); - ERR_FAIL_MSG("ImageLoaderSVG can't create image."); + ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't insert ThorVG picture on canvas."); } res = sw_canvas->draw(); if (res != tvg::Result::Success) { memfree(buffer); - ERR_FAIL_MSG("ImageLoaderSVG can't create image."); + ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't draw ThorVG pictures on canvas."); } res = sw_canvas->sync(); if (res != tvg::Result::Success) { memfree(buffer); - ERR_FAIL_MSG("ImageLoaderSVG can't create image."); + ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't sync ThorVG canvas."); } Vector<uint8_t> image; @@ -136,6 +146,7 @@ void ImageLoaderSVG::create_image_from_string(Ref<Image> p_image, String p_strin memfree(buffer); p_image->set_data(width, height, false, Image::FORMAT_RGBA8, image); + return OK; } void ImageLoaderSVG::get_recognized_extensions(List<String> *p_extensions) const { @@ -145,13 +156,19 @@ void ImageLoaderSVG::get_recognized_extensions(List<String> *p_extensions) const Error ImageLoaderSVG::load_image(Ref<Image> p_image, Ref<FileAccess> p_fileaccess, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale) { String svg = p_fileaccess->get_as_utf8_string(); + Error err; if (p_flags & FLAG_CONVERT_COLORS) { - create_image_from_string(p_image, svg, p_scale, false, forced_color_map); + err = create_image_from_string(p_image, svg, p_scale, false, forced_color_map); } else { - create_image_from_string(p_image, svg, p_scale, false, HashMap<Color, Color>()); + err = create_image_from_string(p_image, svg, p_scale, false, HashMap<Color, Color>()); + } + + if (err != OK) { + return err; + } else if (p_image->is_empty()) { + return ERR_INVALID_DATA; } - ERR_FAIL_COND_V(p_image->is_empty(), FAILED); if (p_flags & FLAG_FORCE_LINEAR) { p_image->srgb_to_linear(); } diff --git a/modules/svg/image_loader_svg.h b/modules/svg/image_loader_svg.h index b0b0963c15..84511f1708 100644 --- a/modules/svg/image_loader_svg.h +++ b/modules/svg/image_loader_svg.h @@ -41,7 +41,7 @@ class ImageLoaderSVG : public ImageFormatLoader { public: static void set_forced_color_map(const HashMap<Color, Color> &p_color_map); - void create_image_from_string(Ref<Image> p_image, String p_string, float p_scale, bool p_upsample, const HashMap<Color, Color> &p_color_map); + Error create_image_from_string(Ref<Image> p_image, String p_string, float p_scale, bool p_upsample, const HashMap<Color, Color> &p_color_map); virtual Error load_image(Ref<Image> p_image, Ref<FileAccess> p_fileaccess, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale) override; virtual void get_recognized_extensions(List<String> *p_extensions) const override; diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub index bd208a8429..3c7a89b705 100644 --- a/modules/text_server_adv/SCsub +++ b/modules/text_server_adv/SCsub @@ -39,6 +39,9 @@ thirdparty_obj = [] freetype_enabled = "freetype" in env.module_list msdfgen_enabled = "msdfgen" in env.module_list +if "svg" in env.module_list: + env_text_server_adv.Prepend(CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/lib"]) + if env["builtin_harfbuzz"]: env_harfbuzz = env_modules.Clone() env_harfbuzz.disable_warnings() diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct index 65b41e46ce..4a363fdd7a 100644 --- a/modules/text_server_adv/gdextension_build/SConstruct +++ b/modules/text_server_adv/gdextension_build/SConstruct @@ -23,6 +23,7 @@ opts.Add(BoolVariable("brotli_enabled", "Use Brotli library", True)) opts.Add(BoolVariable("freetype_enabled", "Use FreeType library", True)) opts.Add(BoolVariable("msdfgen_enabled", "Use MSDFgen library (require FreeType)", True)) opts.Add(BoolVariable("graphite_enabled", "Use Graphite library (require FreeType)", True)) +opts.Add(BoolVariable("thorvg_enabled", "Use ThorVG library (require FreeType)", True)) opts.Add(BoolVariable("static_icu_data", "Use built-in ICU data", True)) opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False)) @@ -34,6 +35,79 @@ if not env["verbose"]: if env["platform"] == "windows" and not env["use_mingw"]: env.AppendUnique(CCFLAGS=["/utf-8"]) # Force to use Unicode encoding. +# ThorVG +if env["thorvg_enabled"] and env["freetype_enabled"]: + env_tvg = env.Clone() + env_tvg.disable_warnings() + + thirdparty_tvg_dir = "../../../thirdparty/thorvg/" + thirdparty_tvg_sources = [ + "src/lib/sw_engine/tvgSwFill.cpp", + "src/lib/sw_engine/tvgSwImage.cpp", + "src/lib/sw_engine/tvgSwMath.cpp", + "src/lib/sw_engine/tvgSwMemPool.cpp", + "src/lib/sw_engine/tvgSwRaster.cpp", + "src/lib/sw_engine/tvgSwRenderer.cpp", + "src/lib/sw_engine/tvgSwRle.cpp", + "src/lib/sw_engine/tvgSwShape.cpp", + "src/lib/sw_engine/tvgSwStroke.cpp", + "src/lib/tvgAccessor.cpp", + "src/lib/tvgBezier.cpp", + "src/lib/tvgCanvas.cpp", + "src/lib/tvgFill.cpp", + "src/lib/tvgGlCanvas.cpp", + "src/lib/tvgInitializer.cpp", + "src/lib/tvgLinearGradient.cpp", + "src/lib/tvgLoader.cpp", + "src/lib/tvgLzw.cpp", + "src/lib/tvgPaint.cpp", + "src/lib/tvgPicture.cpp", + "src/lib/tvgRadialGradient.cpp", + "src/lib/tvgRender.cpp", + "src/lib/tvgSaver.cpp", + "src/lib/tvgScene.cpp", + "src/lib/tvgShape.cpp", + "src/lib/tvgSwCanvas.cpp", + "src/lib/tvgTaskScheduler.cpp", + "src/loaders/external_png/tvgPngLoader.cpp", + "src/loaders/jpg/tvgJpgd.cpp", + "src/loaders/jpg/tvgJpgLoader.cpp", + "src/loaders/raw/tvgRawLoader.cpp", + "src/loaders/svg/tvgSvgCssStyle.cpp", + "src/loaders/svg/tvgSvgLoader.cpp", + "src/loaders/svg/tvgSvgPath.cpp", + "src/loaders/svg/tvgSvgSceneBuilder.cpp", + "src/loaders/svg/tvgSvgUtil.cpp", + "src/loaders/svg/tvgXmlParser.cpp", + "src/loaders/tvg/tvgTvgBinInterpreter.cpp", + "src/loaders/tvg/tvgTvgLoader.cpp", + "src/savers/tvg/tvgTvgSaver.cpp", + ] + thirdparty_tvg_sources = [thirdparty_tvg_dir + file for file in thirdparty_tvg_sources] + + env_tvg.Append( + CPPPATH=[ + "../../../thirdparty/thorvg/inc", + "../../../thirdparty/thorvg/src/lib", + "../../../thirdparty/thorvg/src/lib/sw_engine", + "../../../thirdparty/thorvg/src/loaders/external_png", + "../../../thirdparty/thorvg/src/loaders/jpg", + "../../../thirdparty/thorvg/src/loaders/raw", + "../../../thirdparty/thorvg/src/loaders/svg", + "../../../thirdparty/thorvg/src/loaders/tvg", + "../../../thirdparty/thorvg/src/savers/tvg", + "../../../thirdparty/libpng", + ] + ) + env.Append(CPPPATH=["../../../thirdparty/thorvg/inc", "../../../thirdparty/thorvg/src/lib"]) + env.Append(CPPDEFINES=["MODULE_SVG_ENABLED"]) + + lib = env_tvg.Library( + f'tvg_builtin{env["suffix"]}{env["LIBSUFFIX"]}', + thirdparty_tvg_sources, + ) + env.Append(LIBS=[lib]) + # MSDFGEN if env["msdfgen_enabled"] and env["freetype_enabled"]: env_msdfgen = env.Clone() diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 155da3c67b..512643867b 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -34,6 +34,7 @@ // Headers for building as GDExtension plug-in. #include <godot_cpp/classes/file_access.hpp> +#include <godot_cpp/classes/os.hpp> #include <godot_cpp/classes/project_settings.hpp> #include <godot_cpp/classes/rendering_server.hpp> #include <godot_cpp/classes/translation_server.hpp> @@ -41,6 +42,8 @@ using namespace godot; +#define GLOBAL_GET(m_var) ProjectSettings::get_singleton()->get(m_var) + #else // Headers for building as built-in module. @@ -50,7 +53,7 @@ using namespace godot; #include "core/string/print_string.h" #include "core/string/translation.h" -#include "modules/modules_enabled.gen.h" // For freetype, msdfgen. +#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. #endif @@ -69,6 +72,10 @@ using namespace godot; #include "msdfgen.h" #endif +#ifdef MODULE_SVG_ENABLED +#include "thorvg_svg_in_ot.h" +#endif + /*************************************************************************/ /* bmp_font_t HarfBuzz Bitmap font interface */ /*************************************************************************/ @@ -1346,6 +1353,9 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f memdelete(fd); ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); } +#ifdef MODULE_SVG_ENABLED + FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks()); +#endif } memset(&fd->stream, 0, sizeof(FT_StreamRec)); @@ -1428,11 +1438,13 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f if (fd->face->style_name != nullptr) { p_font_data->style_name = String::utf8((const char *)fd->face->style_name); } + p_font_data->weight = _font_get_weight_by_name(p_font_data->style_name.to_lower()); + p_font_data->stretch = _font_get_stretch_by_name(p_font_data->style_name.to_lower()); p_font_data->style_flags = 0; - if (fd->face->style_flags & FT_STYLE_FLAG_BOLD) { + if ((fd->face->style_flags & FT_STYLE_FLAG_BOLD) || p_font_data->weight >= 700) { p_font_data->style_flags.set_flag(FONT_BOLD); } - if (fd->face->style_flags & FT_STYLE_FLAG_ITALIC) { + if ((fd->face->style_flags & FT_STYLE_FLAG_ITALIC) || _is_ital_style(p_font_data->style_name.to_lower())) { p_font_data->style_flags.set_flag(FONT_ITALIC); } if (fd->face->face_flags & FT_FACE_FLAG_FIXED_WIDTH) { @@ -1888,6 +1900,9 @@ int64_t TextServerAdvanced::_font_get_face_count(const RID &p_font_rid) const { if (!ft_library) { error = FT_Init_FreeType(&ft_library); ERR_FAIL_COND_V_MSG(error != 0, false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); +#ifdef MODULE_SVG_ENABLED + FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks()); +#endif } FT_StreamRec stream; @@ -1955,6 +1970,46 @@ String TextServerAdvanced::_font_get_style_name(const RID &p_font_rid) const { return fd->style_name; } +void TextServerAdvanced::_font_set_weight(const RID &p_font_rid, int64_t p_weight) { + FontAdvanced *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->weight = CLAMP(p_weight, 100, 999); +} + +int64_t TextServerAdvanced::_font_get_weight(const RID &p_font_rid) const { + FontAdvanced *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND_V(!fd, 400); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 400); + return fd->weight; +} + +void TextServerAdvanced::_font_set_stretch(const RID &p_font_rid, int64_t p_stretch) { + FontAdvanced *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->stretch = CLAMP(p_stretch, 50, 200); +} + +int64_t TextServerAdvanced::_font_get_stretch(const RID &p_font_rid) const { + FontAdvanced *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND_V(!fd, 100); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 100); + return fd->stretch; +} + void TextServerAdvanced::_font_set_name(const RID &p_font_rid, const String &p_name) { FontAdvanced *fd = font_owner.get_or_null(p_font_rid); ERR_FAIL_COND(!fd); @@ -2091,6 +2146,22 @@ int64_t TextServerAdvanced::_font_get_fixed_size(const RID &p_font_rid) const { return fd->fixed_size; } +void TextServerAdvanced::_font_set_allow_system_fallback(const RID &p_font_rid, bool p_allow_system_fallback) { + FontAdvanced *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + fd->allow_system_fallback = p_allow_system_fallback; +} + +bool TextServerAdvanced::_font_is_allow_system_fallback(const RID &p_font_rid) const { + FontAdvanced *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); + return fd->allow_system_fallback; +} + void TextServerAdvanced::_font_set_force_autohinter(const RID &p_font_rid, bool p_force_autohinter) { FontAdvanced *fd = font_owner.get_or_null(p_font_rid); ERR_FAIL_COND(!fd); @@ -4620,12 +4691,11 @@ bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) { sd->breaks[pos] = true; } else if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_SOFT) && (ubrk_getRuleStatus(bi) < UBRK_LINE_SOFT_LIMIT)) { sd->breaks[pos] = false; - - int pos_p = pos - 1 - sd->start; - char32_t c = sd->text[pos_p]; - if (pos - sd->start != sd->end && !is_whitespace(c) && (c != 0xfffc)) { - sd->break_inserts++; - } + } + int pos_p = pos - 1 - sd->start; + char32_t c = sd->text[pos_p]; + if (pos - sd->start != sd->end && !is_whitespace(c) && (c != 0xfffc)) { + sd->break_inserts++; } } } @@ -4738,7 +4808,10 @@ bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) { i += (sd_glyphs[i].count - 1); } } - ERR_FAIL_COND_V_MSG(sd_shift != sd->break_inserts, false, "Invalid break insert count!"); + if (sd_shift < sd->break_inserts) { + // Note: should not happen with a normal text, but might be a case with special fonts that substitute a long string (with breaks opportunities in it) with a single glyph (like Font Awesome). + glyphs_new.resize(sd->glyphs.size() + sd_shift); + } if (sd->break_inserts > 0) { sd->glyphs = glyphs_new; @@ -5051,10 +5124,177 @@ _FORCE_INLINE_ void TextServerAdvanced::_add_featuers(const Dictionary &p_source } } -void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_end, hb_script_t p_script, hb_direction_t p_direction, TypedArray<RID> p_fonts, int64_t p_span, int64_t p_fb_index) { +void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_end, hb_script_t p_script, hb_direction_t p_direction, TypedArray<RID> p_fonts, int64_t p_span, int64_t p_fb_index, int64_t p_prev_start, int64_t p_prev_end) { + RID f; int fs = p_sd->spans[p_span].font_size; - if (p_fb_index >= p_fonts.size()) { - // Add fallback glyphs. + + if (p_fb_index >= 0 && p_fb_index < p_fonts.size()) { + // Try font from list. + f = p_fonts[p_fb_index]; + } else if (OS::get_singleton()->has_feature("system_fonts") && p_fonts.size() > 0 && ((p_fb_index == p_fonts.size()) || (p_fb_index > p_fonts.size() && p_start != p_prev_start))) { + // Try system fallback. + RID fdef = p_fonts[0]; + if (_font_is_allow_system_fallback(fdef)) { + String text = p_sd->text.substr(p_start, 1); + String font_name = _font_get_name(fdef); + BitField<FontStyle> font_style = _font_get_style(fdef); + int font_weight = _font_get_weight(fdef); + int font_stretch = _font_get_stretch(fdef); + Dictionary dvar = _font_get_variation_coordinates(fdef); + static int64_t wgth_tag = _name_to_tag("weight"); + static int64_t wdth_tag = _name_to_tag("width"); + static int64_t ital_tag = _name_to_tag("italic"); + if (dvar.has(wgth_tag)) { + font_weight = dvar[wgth_tag].operator int(); + } + if (dvar.has(wdth_tag)) { + font_stretch = dvar[wdth_tag].operator int(); + } + if (dvar.has(ital_tag) && dvar[ital_tag].operator int() == 1) { + font_style.set_flag(TextServer::FONT_ITALIC); + } + + char scr_buffer[5] = { 0, 0, 0, 0, 0 }; + hb_tag_to_string(hb_script_to_iso15924_tag(p_script), scr_buffer); + String script_code = String(scr_buffer); + String locale = (p_sd->spans[p_span].language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : p_sd->spans[p_span].language; + + PackedStringArray fallback_font_name = OS::get_singleton()->get_system_font_path_for_text(font_name, text, locale, script_code, font_weight, font_stretch, font_style & TextServer::FONT_ITALIC); +#ifdef GDEXTENSION + for (int fb = 0; fb < fallback_font_name.size(); fb++) { + const String &E = fallback_font_name[fb]; +#else + for (const String &E : fallback_font_name) { +#endif + SystemFontKey key = SystemFontKey(E, font_style & TextServer::FONT_ITALIC, font_weight, font_stretch, fdef, this); + if (system_fonts.has(key)) { + const SystemFontCache &sysf_cache = system_fonts[key]; + int best_score = 0; + int best_match = -1; + for (int face_idx = 0; face_idx < sysf_cache.var.size(); face_idx++) { + const SystemFontCacheRec &F = sysf_cache.var[face_idx]; + if (unlikely(!_font_has_char(F.rid, text[0]))) { + continue; + } + BitField<FontStyle> style = _font_get_style(F.rid); + int weight = _font_get_weight(F.rid); + int stretch = _font_get_stretch(F.rid); + int score = (20 - Math::abs(weight - font_weight) / 50); + score += (20 - Math::abs(stretch - font_stretch) / 10); + if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) { + score += 30; + } + if (score >= best_score) { + best_score = score; + best_match = face_idx; + } + if (best_score == 70) { + break; + } + } + if (best_match != -1) { + f = sysf_cache.var[best_match].rid; + } + } + if (!f.is_valid()) { + if (system_fonts.has(key)) { + const SystemFontCache &sysf_cache = system_fonts[key]; + if (sysf_cache.max_var == sysf_cache.var.size()) { + // All subfonts already tested, skip. + continue; + } + } + + if (!system_font_data.has(E)) { + system_font_data[E] = FileAccess::get_file_as_bytes(E); + } + + const PackedByteArray &font_data = system_font_data[E]; + + SystemFontCacheRec sysf; + sysf.rid = _create_font(); + _font_set_data_ptr(sysf.rid, font_data.ptr(), font_data.size()); + + Dictionary var = dvar; + // Select matching style from collection. + int best_score = 0; + int best_match = -1; + for (int face_idx = 0; face_idx < _font_get_face_count(sysf.rid); face_idx++) { + _font_set_face_index(sysf.rid, face_idx); + if (unlikely(!_font_has_char(sysf.rid, text[0]))) { + continue; + } + BitField<FontStyle> style = _font_get_style(sysf.rid); + int weight = _font_get_weight(sysf.rid); + int stretch = _font_get_stretch(sysf.rid); + int score = (20 - Math::abs(weight - font_weight) / 50); + score += (20 - Math::abs(stretch - font_stretch) / 10); + if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) { + score += 30; + } + if (score >= best_score) { + best_score = score; + best_match = face_idx; + } + if (best_score == 70) { + break; + } + } + if (best_match == -1) { + _free_rid(sysf.rid); + continue; + } else { + _font_set_face_index(sysf.rid, best_match); + } + sysf.index = best_match; + + // If it's a variable font, apply weight, stretch and italic coordinates to match requested style. + if (best_score != 70) { + Dictionary ftr = _font_supported_variation_list(sysf.rid); + if (ftr.has(wdth_tag)) { + var[wdth_tag] = font_stretch; + _font_set_stretch(sysf.rid, font_stretch); + } + if (ftr.has(wgth_tag)) { + var[wgth_tag] = font_weight; + _font_set_weight(sysf.rid, font_weight); + } + if ((font_style & TextServer::FONT_ITALIC) && ftr.has(ital_tag)) { + var[ital_tag] = 1; + _font_set_style(sysf.rid, _font_get_style(sysf.rid) | TextServer::FONT_ITALIC); + } + } + + _font_set_antialiasing(sysf.rid, key.antialiasing); + _font_set_generate_mipmaps(sysf.rid, key.mipmaps); + _font_set_multichannel_signed_distance_field(sysf.rid, key.msdf); + _font_set_msdf_pixel_range(sysf.rid, key.msdf_range); + _font_set_msdf_size(sysf.rid, key.msdf_source_size); + _font_set_fixed_size(sysf.rid, key.fixed_size); + _font_set_force_autohinter(sysf.rid, key.force_autohinter); + _font_set_hinting(sysf.rid, key.hinting); + _font_set_subpixel_positioning(sysf.rid, key.subpixel_positioning); + _font_set_variation_coordinates(sysf.rid, var); + _font_set_oversampling(sysf.rid, key.oversampling); + _font_set_embolden(sysf.rid, key.embolden); + _font_set_transform(sysf.rid, key.transform); + + if (system_fonts.has(key)) { + system_fonts[key].var.push_back(sysf); + } else { + SystemFontCache &sysf_cache = system_fonts[key]; + sysf_cache.max_var = _font_get_face_count(sysf.rid); + sysf_cache.var.push_back(sysf); + } + f = sysf.rid; + } + break; + } + } + } + + if (!f.is_valid()) { + // No valid font, use fallback hex code boxes. for (int i = p_start; i < p_end; i++) { if (p_sd->preserve_invalid || (p_sd->preserve_control && is_control(p_sd->text[i]))) { Glyph gl; @@ -5085,7 +5325,6 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star return; } - RID f = p_fonts[p_fb_index]; FontAdvanced *fd = font_owner.get_or_null(f); ERR_FAIL_COND(!fd); MutexLock lock(fd->mutex); @@ -5180,7 +5419,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star gl.end = end; gl.count = 0; - gl.font_rid = p_fonts[p_fb_index]; + gl.font_rid = f; gl.font_size = fs; if (glyph_info[i].mask & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) { @@ -5247,7 +5486,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star for (unsigned int i = 0; i < glyph_count; i++) { if ((w[i].flags & GRAPHEME_IS_VALID) == GRAPHEME_IS_VALID) { if (failed_subrun_start != p_end + 1) { - _shape_run(p_sd, failed_subrun_start, failed_subrun_end, p_script, p_direction, p_fonts, p_span, p_fb_index + 1); + _shape_run(p_sd, failed_subrun_start, failed_subrun_end, p_script, p_direction, p_fonts, p_span, p_fb_index + 1, p_start, p_end); failed_subrun_start = p_end + 1; failed_subrun_end = p_start; } @@ -5277,7 +5516,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star } memfree(w); if (failed_subrun_start != p_end + 1) { - _shape_run(p_sd, failed_subrun_start, failed_subrun_end, p_script, p_direction, p_fonts, p_span, p_fb_index + 1); + _shape_run(p_sd, failed_subrun_start, failed_subrun_end, p_script, p_direction, p_fonts, p_span, p_fb_index + 1, p_start, p_end); } p_sd->ascent = MAX(p_sd->ascent, _font_get_ascent(f, fs)); p_sd->descent = MAX(p_sd->descent, _font_get_descent(f, fs)); @@ -5449,7 +5688,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { } fonts.append_array(fonts_scr_only); fonts.append_array(fonts_no_match); - _shape_run(sd, MAX(sd->spans[k].start - sd->start, script_run_start), MIN(sd->spans[k].end - sd->start, script_run_end), sd->script_iter->script_ranges[j].script, bidi_run_direction, fonts, k, 0); + _shape_run(sd, MAX(sd->spans[k].start - sd->start, script_run_start), MIN(sd->spans[k].end - sd->start, script_run_end), sd->script_iter->script_ranges[j].script, bidi_run_direction, fonts, k, 0, 0, 0); } } } @@ -5946,13 +6185,20 @@ String TextServerAdvanced::_strip_diacritics(const String &p_string) const { String result; for (int i = 0; i < normalized_string.length(); i++) { if (u_getCombiningClass(normalized_string[i]) == 0) { +#ifdef GDEXTENSION + result = result + String::chr(normalized_string[i]); +#else result = result + normalized_string[i]; +#endif } } return result; } String TextServerAdvanced::_string_to_upper(const String &p_string, const String &p_language) const { + if (p_string.is_empty()) { + return p_string; + } const String lang = (p_language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : p_language; // Convert to UTF-16. @@ -5972,6 +6218,9 @@ String TextServerAdvanced::_string_to_upper(const String &p_string, const String } String TextServerAdvanced::_string_to_lower(const String &p_string, const String &p_language) const { + if (p_string.is_empty()) { + return p_string; + } const String lang = (p_language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : p_language; // Convert to UTF-16. Char16String utf16 = p_string.utf16(); @@ -6228,6 +6477,17 @@ TextServerAdvanced::TextServerAdvanced() { _bmp_create_font_funcs(); } +void TextServerAdvanced::_cleanup() { + for (const KeyValue<SystemFontKey, SystemFontCache> &E : system_fonts) { + const Vector<SystemFontCacheRec> &sysf_cache = E.value.var; + for (const SystemFontCacheRec &F : sysf_cache) { + _free_rid(F.rid); + } + } + system_fonts.clear(); + system_font_data.clear(); +} + TextServerAdvanced::~TextServerAdvanced() { _bmp_free_font_funcs(); #ifdef MODULE_FREETYPE_ENABLED diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index 33fa1e117e..5e6d2cc2c0 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -88,7 +88,7 @@ using namespace godot; #include "core/templates/rid_owner.h" #include "scene/resources/texture.h" -#include "modules/modules_enabled.gen.h" // For freetype, msdfgen. +#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. #endif @@ -117,6 +117,7 @@ using namespace godot; #include FT_ADVANCES_H #include FT_MULTIPLE_MASTERS_H #include FT_BBOX_H +#include FT_MODULE_H #include FT_CONFIG_OPTIONS_H #if !defined(FT_CONFIG_OPTION_USE_BROTLI) && !defined(_MSC_VER) #warning FreeType is configured without Brotli support, built-in fonts will not be available. @@ -299,6 +300,7 @@ class TextServerAdvanced : public TextServerExtension { int msdf_range = 14; int msdf_source_size = 48; int fixed_size = 0; + bool allow_system_fallback = true; bool force_autohinter = false; TextServer::Hinting hinting = TextServer::HINTING_LIGHT; TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; @@ -310,6 +312,8 @@ class TextServerAdvanced : public TextServerExtension { BitField<TextServer::FontStyle> style_flags = 0; String font_name; String style_name; + int weight = 400; + int stretch = 100; HashMap<Vector2i, FontForSizeAdvanced *, VariantHasher, VariantComparator> cache; @@ -371,6 +375,57 @@ class TextServerAdvanced : public TextServerExtension { _FORCE_INLINE_ double _get_extra_advance(RID p_font_rid, int p_font_size) const; _FORCE_INLINE_ Variant::Type _get_tag_type(int64_t p_tag) const; _FORCE_INLINE_ bool _get_tag_hidden(int64_t p_tag) const; + _FORCE_INLINE_ int _font_get_weight_by_name(const String &p_sty_name) const { + String sty_name = p_sty_name.replace(" ", "").replace("-", ""); + if (sty_name.find("thin") >= 0 || sty_name.find("hairline") >= 0) { + return 100; + } else if (sty_name.find("extralight") >= 0 || sty_name.find("ultralight") >= 0) { + return 200; + } else if (sty_name.find("light") >= 0) { + return 300; + } else if (sty_name.find("semilight") >= 0) { + return 350; + } else if (sty_name.find("regular") >= 0) { + return 400; + } else if (sty_name.find("medium") >= 0) { + return 500; + } else if (sty_name.find("semibold") >= 0 || sty_name.find("demibold") >= 0) { + return 600; + } else if (sty_name.find("bold") >= 0) { + return 700; + } else if (sty_name.find("extrabold") >= 0 || sty_name.find("ultrabold") >= 0) { + return 800; + } else if (sty_name.find("black") >= 0 || sty_name.find("heavy") >= 0) { + return 900; + } else if (sty_name.find("extrablack") >= 0 || sty_name.find("ultrablack") >= 0) { + return 950; + } + return 400; + } + _FORCE_INLINE_ int _font_get_stretch_by_name(const String &p_sty_name) const { + String sty_name = p_sty_name.replace(" ", "").replace("-", ""); + if (sty_name.find("ultracondensed") >= 0) { + return 50; + } else if (sty_name.find("extracondensed") >= 0) { + return 63; + } else if (sty_name.find("condensed") >= 0) { + return 75; + } else if (sty_name.find("semicondensed") >= 0) { + return 87; + } else if (sty_name.find("semiexpanded") >= 0) { + return 113; + } else if (sty_name.find("expanded") >= 0) { + return 125; + } else if (sty_name.find("extraexpanded") >= 0) { + return 150; + } else if (sty_name.find("ultraexpanded") >= 0) { + return 200; + } + return 100; + } + _FORCE_INLINE_ bool _is_ital_style(const String &p_sty_name) const { + return (p_sty_name.find("italic") >= 0) || (p_sty_name.find("oblique") >= 0); + } // Shaped text cache data. struct TrimData { @@ -473,12 +528,87 @@ class TextServerAdvanced : public TextServerExtension { mutable RID_PtrOwner<FontAdvanced> font_owner; mutable RID_PtrOwner<ShapedTextDataAdvanced> shaped_owner; + struct SystemFontKey { + String font_name; + TextServer::FontAntialiasing antialiasing = TextServer::FONT_ANTIALIASING_GRAY; + bool italic = false; + bool mipmaps = false; + bool msdf = false; + bool force_autohinter = false; + int weight = 400; + int stretch = 100; + int msdf_range = 14; + int msdf_source_size = 48; + int fixed_size = 0; + TextServer::Hinting hinting = TextServer::HINTING_LIGHT; + TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; + Dictionary variation_coordinates; + double oversampling = 0.0; + double embolden = 0.0; + Transform2D transform; + + bool operator==(const SystemFontKey &p_b) const { + return (font_name == p_b.font_name) && (antialiasing == p_b.antialiasing) && (italic == p_b.italic) && (mipmaps == p_b.mipmaps) && (msdf == p_b.msdf) && (force_autohinter == p_b.force_autohinter) && (weight == p_b.weight) && (stretch == p_b.stretch) && (msdf_range == p_b.msdf_range) && (msdf_source_size == p_b.msdf_source_size) && (fixed_size == p_b.fixed_size) && (hinting == p_b.hinting) && (subpixel_positioning == p_b.subpixel_positioning) && (variation_coordinates == p_b.variation_coordinates) && (oversampling == p_b.oversampling) && (embolden == p_b.embolden) && (transform == p_b.transform); + } + + SystemFontKey(const String &p_font_name, bool p_italic, int p_weight, int p_stretch, RID p_font, const TextServerAdvanced *p_fb) { + font_name = p_font_name; + italic = p_italic; + weight = p_weight; + stretch = p_stretch; + antialiasing = p_fb->_font_get_antialiasing(p_font); + mipmaps = p_fb->_font_get_generate_mipmaps(p_font); + msdf = p_fb->_font_is_multichannel_signed_distance_field(p_font); + msdf_range = p_fb->_font_get_msdf_pixel_range(p_font); + msdf_source_size = p_fb->_font_get_msdf_size(p_font); + fixed_size = p_fb->_font_get_fixed_size(p_font); + force_autohinter = p_fb->_font_is_force_autohinter(p_font); + hinting = p_fb->_font_get_hinting(p_font); + subpixel_positioning = p_fb->_font_get_subpixel_positioning(p_font); + variation_coordinates = p_fb->_font_get_variation_coordinates(p_font); + oversampling = p_fb->_font_get_oversampling(p_font); + embolden = p_fb->_font_get_embolden(p_font); + transform = p_fb->_font_get_transform(p_font); + } + }; + + struct SystemFontCacheRec { + RID rid; + int index = 0; + }; + + struct SystemFontCache { + Vector<SystemFontCacheRec> var; + int max_var = 0; + }; + + struct SystemFontKeyHasher { + _FORCE_INLINE_ static uint32_t hash(const SystemFontKey &p_a) { + uint32_t hash = p_a.font_name.hash(); + hash = hash_murmur3_one_32(p_a.variation_coordinates.hash(), hash); + hash = hash_murmur3_one_32(p_a.weight, hash); + hash = hash_murmur3_one_32(p_a.stretch, hash); + hash = hash_murmur3_one_32(p_a.msdf_range, hash); + hash = hash_murmur3_one_32(p_a.msdf_source_size, hash); + hash = hash_murmur3_one_32(p_a.fixed_size, hash); + hash = hash_murmur3_one_double(p_a.oversampling, hash); + hash = hash_murmur3_one_double(p_a.embolden, hash); + hash = hash_murmur3_one_real(p_a.transform[0].x, hash); + hash = hash_murmur3_one_real(p_a.transform[0].y, hash); + hash = hash_murmur3_one_real(p_a.transform[1].x, hash); + hash = hash_murmur3_one_real(p_a.transform[1].y, hash); + return hash_fmix32(hash_murmur3_one_32(((int)p_a.mipmaps) | ((int)p_a.msdf << 1) | ((int)p_a.italic << 2) | ((int)p_a.force_autohinter << 3) | ((int)p_a.hinting << 4) | ((int)p_a.subpixel_positioning << 8) | ((int)p_a.antialiasing << 12), hash)); + } + }; + mutable HashMap<SystemFontKey, SystemFontCache, SystemFontKeyHasher> system_fonts; + mutable HashMap<String, PackedByteArray> system_font_data; + void _realign(ShapedTextDataAdvanced *p_sd) const; int64_t _convert_pos(const String &p_utf32, const Char16String &p_utf16, int64_t p_pos) const; int64_t _convert_pos(const ShapedTextDataAdvanced *p_sd, int64_t p_pos) const; int64_t _convert_pos_inv(const ShapedTextDataAdvanced *p_sd, int64_t p_pos) const; bool _shape_substr(ShapedTextDataAdvanced *p_new_sd, const ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_length) const; - void _shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_end, hb_script_t p_script, hb_direction_t p_direction, TypedArray<RID> p_fonts, int64_t p_span, int64_t p_fb_index); + void _shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_end, hb_script_t p_script, hb_direction_t p_direction, TypedArray<RID> p_fonts, int64_t p_span, int64_t p_fb_index, int64_t p_prev_start, int64_t p_prev_end); Glyph _shape_single_glyph(ShapedTextDataAdvanced *p_sd, char32_t p_char, hb_script_t p_script, hb_direction_t p_direction, const RID &p_font, int64_t p_font_size); _FORCE_INLINE_ void _add_featuers(const Dictionary &p_source, Vector<hb_feature_t> &r_ftrs); @@ -567,6 +697,12 @@ public: MODBIND2(font_set_style_name, const RID &, const String &); MODBIND1RC(String, font_get_style_name, const RID &); + MODBIND2(font_set_weight, const RID &, int64_t); + MODBIND1RC(int64_t, font_get_weight, const RID &); + + MODBIND2(font_set_stretch, const RID &, int64_t); + MODBIND1RC(int64_t, font_get_stretch, const RID &); + MODBIND2(font_set_name, const RID &, const String &); MODBIND1RC(String, font_get_name, const RID &); @@ -588,6 +724,9 @@ public: MODBIND2(font_set_fixed_size, const RID &, int64_t); MODBIND1RC(int64_t, font_get_fixed_size, const RID &); + MODBIND2(font_set_allow_system_fallback, const RID &, bool); + MODBIND1RC(bool, font_is_allow_system_fallback, const RID &); + MODBIND2(font_set_force_autohinter, const RID &, bool); MODBIND1RC(bool, font_is_force_autohinter, const RID &); @@ -786,6 +925,8 @@ public: MODBIND2RC(String, string_to_upper, const String &, const String &); MODBIND2RC(String, string_to_lower, const String &, const String &); + MODBIND0(cleanup); + TextServerAdvanced(); ~TextServerAdvanced(); }; diff --git a/modules/text_server_adv/thorvg_bounds_iterator.cpp b/modules/text_server_adv/thorvg_bounds_iterator.cpp new file mode 100644 index 0000000000..54a6136134 --- /dev/null +++ b/modules/text_server_adv/thorvg_bounds_iterator.cpp @@ -0,0 +1,70 @@ +/*************************************************************************/ +/* thorvg_bounds_iterator.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifdef GDEXTENSION +// Headers for building as GDExtension plug-in. + +#include <godot_cpp/godot.hpp> + +using namespace godot; + +#else +// Headers for building as built-in module. + +#include "core/typedefs.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#endif + +#ifdef MODULE_SVG_ENABLED + +#include "thorvg_bounds_iterator.h" + +#include <tvgIteratorAccessor.h> +#include <tvgPaint.h> + +// This function uses private ThorVG API to get bounding box of top level children elements. + +void tvg_get_bounds(tvg::Picture *p_picture, float &r_min_x, float &r_min_y, float &r_max_x, float &r_max_y) { + tvg::IteratorAccessor itrAccessor; + if (tvg::Iterator *it = itrAccessor.iterator(p_picture)) { + while (const tvg::Paint *child = it->next()) { + float x = 0, y = 0, w = 0, h = 0; + child->bounds(&x, &y, &w, &h, true); + r_min_x = MIN(x, r_min_x); + r_min_y = MIN(y, r_min_y); + r_max_x = MAX(x + w, r_max_x); + r_max_y = MAX(y + h, r_max_y); + } + delete (it); + } +} + +#endif // MODULE_SVG_ENABLED diff --git a/modules/text_server_adv/thorvg_bounds_iterator.h b/modules/text_server_adv/thorvg_bounds_iterator.h new file mode 100644 index 0000000000..e54e30eaa2 --- /dev/null +++ b/modules/text_server_adv/thorvg_bounds_iterator.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* thorvg_bounds_iterator.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef THORVG_BOUNDS_ITERATOR_H +#define THORVG_BOUNDS_ITERATOR_H + +#ifdef GDEXTENSION +// Headers for building as GDExtension plug-in. + +#include <godot_cpp/core/mutex_lock.hpp> +#include <godot_cpp/godot.hpp> + +using namespace godot; + +#else +// Headers for building as built-in module. + +#include "core/typedefs.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#endif + +#ifdef MODULE_SVG_ENABLED + +#include <thorvg.h> + +void tvg_get_bounds(tvg::Picture *p_picture, float &r_min_x, float &r_min_y, float &r_max_x, float &r_max_y); + +#endif // MODULE_SVG_ENABLED + +#endif // THORVG_BOUNDS_ITERATOR_H diff --git a/modules/text_server_adv/thorvg_svg_in_ot.cpp b/modules/text_server_adv/thorvg_svg_in_ot.cpp new file mode 100644 index 0000000000..7863ab67fa --- /dev/null +++ b/modules/text_server_adv/thorvg_svg_in_ot.cpp @@ -0,0 +1,320 @@ +/*************************************************************************/ +/* thorvg_svg_in_ot.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifdef GDEXTENSION +// Headers for building as GDExtension plug-in. + +#include <godot_cpp/classes/xml_parser.hpp> +#include <godot_cpp/core/mutex_lock.hpp> +#include <godot_cpp/godot.hpp> +#include <godot_cpp/templates/vector.hpp> + +using namespace godot; + +#else +// Headers for building as built-in module. + +#include "core/error/error_macros.h" +#include "core/io/xml_parser.h" +#include "core/os/memory.h" +#include "core/os/os.h" +#include "core/string/ustring.h" +#include "core/typedefs.h" +#include "core/variant/variant.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#endif + +#ifdef MODULE_SVG_ENABLED + +#include "thorvg_bounds_iterator.h" +#include "thorvg_svg_in_ot.h" + +#include <freetype/otsvg.h> +#include <ft2build.h> + +#include <math.h> +#include <stdlib.h> + +FT_Error tvg_svg_in_ot_init(FT_Pointer *p_state) { + *p_state = memnew(TVG_State); + + return FT_Err_Ok; +} + +void tvg_svg_in_ot_free(FT_Pointer *p_state) { + TVG_State *state = *reinterpret_cast<TVG_State **>(p_state); + memdelete(state); +} + +FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Pointer *p_state) { + TVG_State *state = *reinterpret_cast<TVG_State **>(p_state); + if (!state) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG in OT state not initialized."); + } + MutexLock lock(state->mutex); + + FT_SVG_Document document = (FT_SVG_Document)p_slot->other; + FT_Size_Metrics metrics = document->metrics; + + GL_State &gl_state = state->glyph_map[p_slot->glyph_index]; + if (!gl_state.ready) { + Ref<XMLParser> parser; + parser.instantiate(); +#ifdef GDEXTENSION + PackedByteArray data; + data.resize(document->svg_document_length); + memcpy(data.ptrw(), document->svg_document, document->svg_document_length); + parser->open_buffer(data); +#else + parser->_open_buffer((const uint8_t *)document->svg_document, document->svg_document_length); +#endif + + float aspect = 1.0f; + String xml_body; + while (parser->read() == OK) { + if (parser->has_attribute("id")) { +#ifdef GDEXTENSION + const String &gl_name = parser->get_named_attribute_value("id"); +#else + const String &gl_name = parser->get_attribute_value("id"); +#endif + if (gl_name.begins_with("glyph")) { + int dot_pos = gl_name.find("."); + int64_t gl_idx = gl_name.substr(5, (dot_pos > 0) ? dot_pos - 5 : -1).to_int(); + if (p_slot->glyph_index != gl_idx) { + parser->skip_section(); + continue; + } + } + } + if (parser->get_node_type() == XMLParser::NODE_ELEMENT && parser->get_node_name() == "svg") { + if (parser->has_attribute("viewBox")) { +#ifdef GDEXTENSION + PackedStringArray vb = parser->get_named_attribute_value("viewBox").split(" "); +#else + Vector<String> vb = parser->get_attribute_value("viewBox").split(" "); +#endif + + if (vb.size() == 4) { + aspect = vb[2].to_float() / vb[3].to_float(); + } + } + continue; + } +#ifdef GDEXTENSION + if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { + xml_body = xml_body + "<" + parser->get_node_name(); + for (int i = 0; i < parser->get_attribute_count(); i++) { + xml_body = xml_body + " " + parser->get_attribute_name(i) + "=\"" + parser->get_attribute_value(i) + "\""; + } + xml_body = xml_body + ">"; + } else if (parser->get_node_type() == XMLParser::NODE_TEXT) { + xml_body = xml_body + parser->get_node_data(); + } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) { + xml_body = xml_body + "</" + parser->get_node_name() + ">"; + } +#else + if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { + xml_body += vformat("<%s", parser->get_node_name()); + for (int i = 0; i < parser->get_attribute_count(); i++) { + xml_body += vformat(" %s=\"%s\"", parser->get_attribute_name(i), parser->get_attribute_value(i)); + } + xml_body += ">"; + } else if (parser->get_node_type() == XMLParser::NODE_TEXT) { + xml_body += parser->get_node_data(); + } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) { + xml_body += vformat("</%s>", parser->get_node_name()); + } +#endif + } + String temp_xml = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 0 0\">" + xml_body; + + std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen(); + tvg::Result result = picture->load(temp_xml.utf8().get_data(), temp_xml.utf8().length(), "svg+xml", false); + if (result != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (bounds detection)."); + } + + float min_x = INFINITY, min_y = INFINITY, max_x = -INFINITY, max_y = -INFINITY; + tvg_get_bounds(picture.get(), min_x, min_y, max_x, max_y); + + float new_h = (max_y - min_y); + float new_w = (max_x - min_x); + + if (new_h * aspect >= new_w) { + new_w = (new_h * aspect); + } else { + new_h = (new_w / aspect); + } + +#ifdef GDEXTENSION + gl_state.xml_code = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"" + rtos(min_x) + " " + rtos(min_y) + " " + rtos(new_w) + " " + rtos(new_h) + "\">" + xml_body; +#else + gl_state.xml_code = vformat("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"%f %f %f %f\">", min_x, min_y, new_w, new_h) + xml_body; +#endif + + picture = tvg::Picture::gen(); + result = picture->load(gl_state.xml_code.utf8().get_data(), gl_state.xml_code.utf8().length(), "svg+xml", false); + if (result != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph metrics)."); + } + + float x_svg_to_out, y_svg_to_out; + x_svg_to_out = (float)metrics.x_ppem / new_w; + y_svg_to_out = (float)metrics.y_ppem / new_h; + + gl_state.m.e11 = (double)document->transform.xx / (1 << 16) * x_svg_to_out; + gl_state.m.e12 = -(double)document->transform.xy / (1 << 16) * x_svg_to_out; + gl_state.m.e21 = -(double)document->transform.yx / (1 << 16) * y_svg_to_out; + gl_state.m.e22 = (double)document->transform.yy / (1 << 16) * y_svg_to_out; + gl_state.m.e13 = (double)document->delta.x / 64 * new_w / metrics.x_ppem; + gl_state.m.e23 = -(double)document->delta.y / 64 * new_h / metrics.y_ppem; + gl_state.m.e31 = 0; + gl_state.m.e32 = 0; + gl_state.m.e33 = 1; + + result = picture->transform(gl_state.m); + if (result != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to apply transform to SVG document."); + } + + result = picture->bounds(&gl_state.x, &gl_state.y, &gl_state.w, &gl_state.h, true); + if (result != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to get SVG bounds."); + } + + gl_state.bmp_y = -min_y * gl_state.h / new_h; + gl_state.bmp_x = min_x * gl_state.w / new_w; + + gl_state.ready = true; + } + + p_slot->bitmap_left = (FT_Int)gl_state.bmp_x; + p_slot->bitmap_top = (FT_Int)gl_state.bmp_y; + + float tmp = ceil(gl_state.h); + p_slot->bitmap.rows = (unsigned int)tmp; + tmp = ceil(gl_state.w); + p_slot->bitmap.width = (unsigned int)tmp; + p_slot->bitmap.pitch = (int)p_slot->bitmap.width * 4; + p_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA; + + float metrics_width, metrics_height; + float horiBearingX, horiBearingY; + float vertBearingX, vertBearingY; + + metrics_width = (float)gl_state.w; + metrics_height = (float)gl_state.h; + horiBearingX = (float)gl_state.x; + horiBearingY = (float)-gl_state.y; + vertBearingX = p_slot->metrics.horiBearingX / 64.0f - p_slot->metrics.horiAdvance / 64.0f / 2; + vertBearingY = (p_slot->metrics.vertAdvance / 64.0f - p_slot->metrics.height / 64.0f) / 2; + + tmp = roundf(metrics_width * 64); + p_slot->metrics.width = (FT_Pos)tmp; + tmp = roundf(metrics_height * 64); + p_slot->metrics.height = (FT_Pos)tmp; + + p_slot->metrics.horiBearingX = (FT_Pos)(horiBearingX * 64); + p_slot->metrics.horiBearingY = (FT_Pos)(horiBearingY * 64); + p_slot->metrics.vertBearingX = (FT_Pos)(vertBearingX * 64); + p_slot->metrics.vertBearingY = (FT_Pos)(vertBearingY * 64); + + if (p_slot->metrics.vertAdvance == 0) { + p_slot->metrics.vertAdvance = (FT_Pos)(metrics_height * 1.2f * 64); + } + + return FT_Err_Ok; +} + +FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state) { + TVG_State *state = *reinterpret_cast<TVG_State **>(p_state); + if (!state) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG in OT state not initialized."); + } + MutexLock lock(state->mutex); + + if (!state->glyph_map.has(p_slot->glyph_index)) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG glyph not loaded."); + } + + GL_State &gl_state = state->glyph_map[p_slot->glyph_index]; + ERR_FAIL_COND_V_MSG(!gl_state.ready, FT_Err_Invalid_SVG_Document, "SVG glyph not ready."); + + std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen(); + tvg::Result res = picture->load(gl_state.xml_code.utf8().get_data(), gl_state.xml_code.utf8().length(), "svg+xml", false); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph rendering)."); + } + res = picture->transform(gl_state.m); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to apply transform to SVG document."); + } + + std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen(); + res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888_STRAIGHT); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to create SVG canvas."); + } + res = sw_canvas->push(std::move(picture)); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to set SVG canvas source."); + } + res = sw_canvas->draw(); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to draw to SVG canvas."); + } + res = sw_canvas->sync(); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to sync SVG canvas."); + } + + state->glyph_map.erase(p_slot->glyph_index); + + p_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA; + p_slot->bitmap.num_grays = 256; + p_slot->format = FT_GLYPH_FORMAT_BITMAP; + + return FT_Err_Ok; +} + +SVG_RendererHooks tvg_svg_in_ot_hooks = { + (SVG_Lib_Init_Func)tvg_svg_in_ot_init, + (SVG_Lib_Free_Func)tvg_svg_in_ot_free, + (SVG_Lib_Render_Func)tvg_svg_in_ot_render, + (SVG_Lib_Preset_Slot_Func)tvg_svg_in_ot_preset_slot, +}; + +SVG_RendererHooks *get_tvg_svg_in_ot_hooks() { + return &tvg_svg_in_ot_hooks; +} + +#endif // MODULE_SVG_ENABLED diff --git a/modules/text_server_adv/thorvg_svg_in_ot.h b/modules/text_server_adv/thorvg_svg_in_ot.h new file mode 100644 index 0000000000..b2816193d9 --- /dev/null +++ b/modules/text_server_adv/thorvg_svg_in_ot.h @@ -0,0 +1,86 @@ +/*************************************************************************/ +/* thorvg_svg_in_ot.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef THORVG_SVG_IN_OT_H +#define THORVG_SVG_IN_OT_H + +#ifdef GDEXTENSION +// Headers for building as GDExtension plug-in. + +#include <godot_cpp/core/mutex_lock.hpp> +#include <godot_cpp/godot.hpp> +#include <godot_cpp/templates/hash_map.hpp> + +using namespace godot; + +#else +// Headers for building as built-in module. + +#include "core/os/mutex.h" +#include "core/templates/hash_map.h" +#include "core/typedefs.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#endif + +#ifdef MODULE_SVG_ENABLED + +#include <freetype/freetype.h> +#include <freetype/otsvg.h> +#include <ft2build.h> +#include <thorvg.h> + +struct GL_State { + bool ready = false; + float bmp_x = 0; + float bmp_y = 0; + float x = 0; + float y = 0; + float w = 0; + float h = 0; + String xml_code; + tvg::Matrix m; +}; + +struct TVG_State { + Mutex mutex; + HashMap<uint32_t, GL_State> glyph_map; +}; + +FT_Error tvg_svg_in_ot_init(FT_Pointer *p_state); +void tvg_svg_in_ot_free(FT_Pointer *p_state); +FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Pointer *p_state); +FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state); + +SVG_RendererHooks *get_tvg_svg_in_ot_hooks(); + +#endif // MODULE_SVG_ENABLED + +#endif // THORVG_SVG_IN_OT_H diff --git a/modules/text_server_fb/SCsub b/modules/text_server_fb/SCsub index 429d2e1fdc..f1d57ec4d3 100644 --- a/modules/text_server_fb/SCsub +++ b/modules/text_server_fb/SCsub @@ -8,6 +8,9 @@ msdfgen_enabled = "msdfgen" in env.module_list env_text_server_fb = env_modules.Clone() +if "svg" in env.module_list: + env_text_server_fb.Prepend(CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/lib"]) + if env["builtin_msdfgen"] and msdfgen_enabled: env_text_server_fb.Prepend(CPPPATH=["#thirdparty/msdfgen"]) diff --git a/modules/text_server_fb/gdextension_build/SConstruct b/modules/text_server_fb/gdextension_build/SConstruct index 8ed8f61a43..7b4c548a21 100644 --- a/modules/text_server_fb/gdextension_build/SConstruct +++ b/modules/text_server_fb/gdextension_build/SConstruct @@ -22,6 +22,7 @@ opts = Variables([], ARGUMENTS) opts.Add(BoolVariable("brotli_enabled", "Use Brotli library", True)) opts.Add(BoolVariable("freetype_enabled", "Use FreeType library", True)) opts.Add(BoolVariable("msdfgen_enabled", "Use MSDFgen library (require FreeType)", True)) +opts.Add(BoolVariable("thorvg_enabled", "Use ThorVG library (require FreeType)", True)) opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False)) opts.Update(env) @@ -29,6 +30,79 @@ opts.Update(env) if not env["verbose"]: methods.no_verbose(sys, env) +# ThorVG +if env["thorvg_enabled"] and env["freetype_enabled"]: + env_tvg = env.Clone() + env_tvg.disable_warnings() + + thirdparty_tvg_dir = "../../../thirdparty/thorvg/" + thirdparty_tvg_sources = [ + "src/lib/sw_engine/tvgSwFill.cpp", + "src/lib/sw_engine/tvgSwImage.cpp", + "src/lib/sw_engine/tvgSwMath.cpp", + "src/lib/sw_engine/tvgSwMemPool.cpp", + "src/lib/sw_engine/tvgSwRaster.cpp", + "src/lib/sw_engine/tvgSwRenderer.cpp", + "src/lib/sw_engine/tvgSwRle.cpp", + "src/lib/sw_engine/tvgSwShape.cpp", + "src/lib/sw_engine/tvgSwStroke.cpp", + "src/lib/tvgAccessor.cpp", + "src/lib/tvgBezier.cpp", + "src/lib/tvgCanvas.cpp", + "src/lib/tvgFill.cpp", + "src/lib/tvgGlCanvas.cpp", + "src/lib/tvgInitializer.cpp", + "src/lib/tvgLinearGradient.cpp", + "src/lib/tvgLoader.cpp", + "src/lib/tvgLzw.cpp", + "src/lib/tvgPaint.cpp", + "src/lib/tvgPicture.cpp", + "src/lib/tvgRadialGradient.cpp", + "src/lib/tvgRender.cpp", + "src/lib/tvgSaver.cpp", + "src/lib/tvgScene.cpp", + "src/lib/tvgShape.cpp", + "src/lib/tvgSwCanvas.cpp", + "src/lib/tvgTaskScheduler.cpp", + "src/loaders/external_png/tvgPngLoader.cpp", + "src/loaders/jpg/tvgJpgd.cpp", + "src/loaders/jpg/tvgJpgLoader.cpp", + "src/loaders/raw/tvgRawLoader.cpp", + "src/loaders/svg/tvgSvgCssStyle.cpp", + "src/loaders/svg/tvgSvgLoader.cpp", + "src/loaders/svg/tvgSvgPath.cpp", + "src/loaders/svg/tvgSvgSceneBuilder.cpp", + "src/loaders/svg/tvgSvgUtil.cpp", + "src/loaders/svg/tvgXmlParser.cpp", + "src/loaders/tvg/tvgTvgBinInterpreter.cpp", + "src/loaders/tvg/tvgTvgLoader.cpp", + "src/savers/tvg/tvgTvgSaver.cpp", + ] + thirdparty_tvg_sources = [thirdparty_tvg_dir + file for file in thirdparty_tvg_sources] + + env_tvg.Append( + CPPPATH=[ + "../../../thirdparty/thorvg/inc", + "../../../thirdparty/thorvg/src/lib", + "../../../thirdparty/thorvg/src/lib/sw_engine", + "../../../thirdparty/thorvg/src/loaders/external_png", + "../../../thirdparty/thorvg/src/loaders/jpg", + "../../../thirdparty/thorvg/src/loaders/raw", + "../../../thirdparty/thorvg/src/loaders/svg", + "../../../thirdparty/thorvg/src/loaders/tvg", + "../../../thirdparty/thorvg/src/savers/tvg", + "../../../thirdparty/libpng", + ] + ) + env.Append(CPPPATH=["../../../thirdparty/thorvg/inc", "../../../thirdparty/thorvg/src/lib"]) + env.Append(CPPDEFINES=["MODULE_SVG_ENABLED"]) + + lib = env_tvg.Library( + f'tvg_builtin{env["suffix"]}{env["LIBSUFFIX"]}', + thirdparty_tvg_sources, + ) + env.Append(LIBS=[lib]) + # MSDFGEN if env["msdfgen_enabled"] and env["freetype_enabled"]: env_msdfgen = env.Clone() diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 999870b904..353d370f14 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -34,6 +34,7 @@ // Headers for building as GDExtension plug-in. #include <godot_cpp/classes/file_access.hpp> +#include <godot_cpp/classes/os.hpp> #include <godot_cpp/classes/project_settings.hpp> #include <godot_cpp/classes/rendering_server.hpp> #include <godot_cpp/classes/translation_server.hpp> @@ -41,15 +42,18 @@ using namespace godot; +#define GLOBAL_GET(m_var) ProjectSettings::get_singleton()->get(m_var) + #else // Headers for building as built-in module. #include "core/config/project_settings.h" #include "core/error/error_macros.h" #include "core/string/print_string.h" +#include "core/string/translation.h" #include "core/string/ucaps.h" -#include "modules/modules_enabled.gen.h" // For freetype, msdfgen. +#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. #endif @@ -62,6 +66,10 @@ using namespace godot; #include "msdfgen.h" #endif +#ifdef MODULE_SVG_ENABLED +#include "thorvg_svg_in_ot.h" +#endif + /*************************************************************************/ #define OT_TAG(c1, c2, c3, c4) ((int32_t)((((uint32_t)(c1)&0xff) << 24) | (((uint32_t)(c2)&0xff) << 16) | (((uint32_t)(c3)&0xff) << 8) | ((uint32_t)(c4)&0xff))) @@ -771,6 +779,9 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f memdelete(fd); ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); } +#ifdef MODULE_SVG_ENABLED + FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks()); +#endif } memset(&fd->stream, 0, sizeof(FT_StreamRec)); @@ -843,11 +854,13 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f if (fd->face->style_name != nullptr) { p_font_data->style_name = String::utf8((const char *)fd->face->style_name); } + p_font_data->weight = _font_get_weight_by_name(p_font_data->style_name.to_lower()); + p_font_data->stretch = _font_get_stretch_by_name(p_font_data->style_name.to_lower()); p_font_data->style_flags = 0; - if (fd->face->style_flags & FT_STYLE_FLAG_BOLD) { + if ((fd->face->style_flags & FT_STYLE_FLAG_BOLD) || p_font_data->weight >= 700) { p_font_data->style_flags.set_flag(FONT_BOLD); } - if (fd->face->style_flags & FT_STYLE_FLAG_ITALIC) { + if ((fd->face->style_flags & FT_STYLE_FLAG_ITALIC) || _is_ital_style(p_font_data->style_name.to_lower())) { p_font_data->style_flags.set_flag(FONT_ITALIC); } if (fd->face->face_flags & FT_FACE_FLAG_FIXED_WIDTH) { @@ -992,6 +1005,9 @@ int64_t TextServerFallback::_font_get_face_count(const RID &p_font_rid) const { if (!ft_library) { error = FT_Init_FreeType(&ft_library); ERR_FAIL_COND_V_MSG(error != 0, false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'."); +#ifdef MODULE_SVG_ENABLED + FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks()); +#endif } FT_StreamRec stream; @@ -1049,6 +1065,46 @@ String TextServerFallback::_font_get_style_name(const RID &p_font_rid) const { return fd->style_name; } +void TextServerFallback::_font_set_weight(const RID &p_font_rid, int64_t p_weight) { + FontFallback *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->weight = CLAMP(p_weight, 100, 999); +} + +int64_t TextServerFallback::_font_get_weight(const RID &p_font_rid) const { + FontFallback *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND_V(!fd, 400); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 400); + return fd->weight; +} + +void TextServerFallback::_font_set_stretch(const RID &p_font_rid, int64_t p_stretch) { + FontFallback *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + fd->stretch = CLAMP(p_stretch, 50, 200); +} + +int64_t TextServerFallback::_font_get_stretch(const RID &p_font_rid) const { + FontFallback *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND_V(!fd, 100); + + MutexLock lock(fd->mutex); + Vector2i size = _get_size(fd, 16); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 100); + return fd->stretch; +} + void TextServerFallback::_font_set_name(const RID &p_font_rid, const String &p_name) { FontFallback *fd = font_owner.get_or_null(p_font_rid); ERR_FAIL_COND(!fd); @@ -1185,6 +1241,22 @@ int64_t TextServerFallback::_font_get_fixed_size(const RID &p_font_rid) const { return fd->fixed_size; } +void TextServerFallback::_font_set_allow_system_fallback(const RID &p_font_rid, bool p_allow_system_fallback) { + FontFallback *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND(!fd); + + MutexLock lock(fd->mutex); + fd->allow_system_fallback = p_allow_system_fallback; +} + +bool TextServerFallback::_font_is_allow_system_fallback(const RID &p_font_rid) const { + FontFallback *fd = font_owner.get_or_null(p_font_rid); + ERR_FAIL_COND_V(!fd, false); + + MutexLock lock(fd->mutex); + return fd->allow_system_fallback; +} + void TextServerFallback::_font_set_force_autohinter(const RID &p_font_rid, bool p_force_autohinter) { FontFallback *fd = font_owner.get_or_null(p_font_rid); ERR_FAIL_COND(!fd); @@ -3591,6 +3663,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { sd->glyphs.push_back(gl); } else { // Text span. + RID prev_font; for (int j = span.start; j < span.end; j++) { Glyph gl; gl.start = j; @@ -3611,6 +3684,170 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { break; } } + if (!gl.font_rid.is_valid() && prev_font.is_valid()) { + if (_font_has_char(prev_font, gl.index)) { + gl.font_rid = prev_font; + } + } + if (!gl.font_rid.is_valid() && OS::get_singleton()->has_feature("system_fonts") && span.fonts.size() > 0) { + // Try system fallback. + RID fdef = span.fonts[0]; + if (_font_is_allow_system_fallback(fdef)) { + String text = sd->text.substr(j, 1); + String font_name = _font_get_name(fdef); + BitField<FontStyle> font_style = _font_get_style(fdef); + int font_weight = _font_get_weight(fdef); + int font_stretch = _font_get_stretch(fdef); + Dictionary dvar = _font_get_variation_coordinates(fdef); + static int64_t wgth_tag = _name_to_tag("weight"); + static int64_t wdth_tag = _name_to_tag("width"); + static int64_t ital_tag = _name_to_tag("italic"); + if (dvar.has(wgth_tag)) { + font_weight = dvar[wgth_tag].operator int(); + } + if (dvar.has(wdth_tag)) { + font_stretch = dvar[wdth_tag].operator int(); + } + if (dvar.has(ital_tag) && dvar[ital_tag].operator int() == 1) { + font_style.set_flag(TextServer::FONT_ITALIC); + } + + String locale = (span.language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : span.language; + + PackedStringArray fallback_font_name = OS::get_singleton()->get_system_font_path_for_text(font_name, text, locale, String(), font_weight, font_stretch, font_style & TextServer::FONT_ITALIC); +#ifdef GDEXTENSION + for (int fb = 0; fb < fallback_font_name.size(); fb++) { + const String &E = fallback_font_name[fb]; +#else + for (const String &E : fallback_font_name) { +#endif + SystemFontKey key = SystemFontKey(E, font_style & TextServer::FONT_ITALIC, font_weight, font_stretch, fdef, this); + if (system_fonts.has(key)) { + const SystemFontCache &sysf_cache = system_fonts[key]; + int best_score = 0; + int best_match = -1; + for (int face_idx = 0; face_idx < sysf_cache.var.size(); face_idx++) { + const SystemFontCacheRec &F = sysf_cache.var[face_idx]; + if (unlikely(!_font_has_char(F.rid, text[0]))) { + continue; + } + BitField<FontStyle> style = _font_get_style(F.rid); + int weight = _font_get_weight(F.rid); + int stretch = _font_get_stretch(F.rid); + int score = (20 - Math::abs(weight - font_weight) / 50); + score += (20 - Math::abs(stretch - font_stretch) / 10); + if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) { + score += 30; + } + if (score >= best_score) { + best_score = score; + best_match = face_idx; + } + if (best_score == 70) { + break; + } + } + if (best_match != -1) { + gl.font_rid = sysf_cache.var[best_match].rid; + } + } + if (!gl.font_rid.is_valid()) { + if (system_fonts.has(key)) { + const SystemFontCache &sysf_cache = system_fonts[key]; + if (sysf_cache.max_var == sysf_cache.var.size()) { + // All subfonts already tested, skip. + continue; + } + } + + if (!system_font_data.has(E)) { + system_font_data[E] = FileAccess::get_file_as_bytes(E); + } + + const PackedByteArray &font_data = system_font_data[E]; + + SystemFontCacheRec sysf; + sysf.rid = _create_font(); + _font_set_data_ptr(sysf.rid, font_data.ptr(), font_data.size()); + + Dictionary var = dvar; + // Select matching style from collection. + int best_score = 0; + int best_match = -1; + for (int face_idx = 0; face_idx < _font_get_face_count(sysf.rid); face_idx++) { + _font_set_face_index(sysf.rid, face_idx); + if (unlikely(!_font_has_char(sysf.rid, text[0]))) { + continue; + } + BitField<FontStyle> style = _font_get_style(sysf.rid); + int weight = _font_get_weight(sysf.rid); + int stretch = _font_get_stretch(sysf.rid); + int score = (20 - Math::abs(weight - font_weight) / 50); + score += (20 - Math::abs(stretch - font_stretch) / 10); + if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) { + score += 30; + } + if (score >= best_score) { + best_score = score; + best_match = face_idx; + } + if (best_score == 70) { + break; + } + } + if (best_match == -1) { + _free_rid(sysf.rid); + continue; + } else { + _font_set_face_index(sysf.rid, best_match); + } + sysf.index = best_match; + + // If it's a variable font, apply weight, stretch and italic coordinates to match requested style. + if (best_score != 70) { + Dictionary ftr = _font_supported_variation_list(sysf.rid); + if (ftr.has(wdth_tag)) { + var[wdth_tag] = font_stretch; + _font_set_stretch(sysf.rid, font_stretch); + } + if (ftr.has(wgth_tag)) { + var[wgth_tag] = font_weight; + _font_set_weight(sysf.rid, font_weight); + } + if ((font_style & TextServer::FONT_ITALIC) && ftr.has(ital_tag)) { + var[ital_tag] = 1; + _font_set_style(sysf.rid, _font_get_style(sysf.rid) | TextServer::FONT_ITALIC); + } + } + + _font_set_antialiasing(sysf.rid, key.antialiasing); + _font_set_generate_mipmaps(sysf.rid, key.mipmaps); + _font_set_multichannel_signed_distance_field(sysf.rid, key.msdf); + _font_set_msdf_pixel_range(sysf.rid, key.msdf_range); + _font_set_msdf_size(sysf.rid, key.msdf_source_size); + _font_set_fixed_size(sysf.rid, key.fixed_size); + _font_set_force_autohinter(sysf.rid, key.force_autohinter); + _font_set_hinting(sysf.rid, key.hinting); + _font_set_subpixel_positioning(sysf.rid, key.subpixel_positioning); + _font_set_variation_coordinates(sysf.rid, var); + _font_set_oversampling(sysf.rid, key.oversampling); + _font_set_embolden(sysf.rid, key.embolden); + _font_set_transform(sysf.rid, key.transform); + + if (system_fonts.has(key)) { + system_fonts[key].var.push_back(sysf); + } else { + SystemFontCache &sysf_cache = system_fonts[key]; + sysf_cache.max_var = _font_get_face_count(sysf.rid); + sysf_cache.var.push_back(sysf); + } + gl.font_rid = sysf.rid; + } + break; + } + } + } + prev_font = gl.font_rid; double scale = _font_get_scale(gl.font_rid, gl.font_size); if (gl.font_rid.is_valid()) { @@ -3881,6 +4118,17 @@ TextServerFallback::TextServerFallback() { _insert_feature_sets(); }; +void TextServerFallback::_cleanup() { + for (const KeyValue<SystemFontKey, SystemFontCache> &E : system_fonts) { + const Vector<SystemFontCacheRec> &sysf_cache = E.value.var; + for (const SystemFontCacheRec &F : sysf_cache) { + _free_rid(F.rid); + } + } + system_fonts.clear(); + system_font_data.clear(); +} + TextServerFallback::~TextServerFallback() { #ifdef MODULE_FREETYPE_ENABLED if (ft_library != nullptr) { diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index 151ae13904..f8a05f9433 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -87,7 +87,7 @@ using namespace godot; #include "core/templates/rid_owner.h" #include "scene/resources/texture.h" -#include "modules/modules_enabled.gen.h" // For freetype, msdfgen. +#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. #endif @@ -101,6 +101,7 @@ using namespace godot; #include FT_ADVANCES_H #include FT_MULTIPLE_MASTERS_H #include FT_BBOX_H +#include FT_MODULE_H #include FT_CONFIG_OPTIONS_H #if !defined(FT_CONFIG_OPTION_USE_BROTLI) && !defined(_MSC_VER) #warning FreeType is configured without Brotli support, built-in fonts will not be available. @@ -255,6 +256,7 @@ class TextServerFallback : public TextServerExtension { int msdf_source_size = 48; int fixed_size = 0; bool force_autohinter = false; + bool allow_system_fallback = true; TextServer::Hinting hinting = TextServer::HINTING_LIGHT; TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; Dictionary variation_coordinates; @@ -265,6 +267,8 @@ class TextServerFallback : public TextServerExtension { BitField<TextServer::FontStyle> style_flags = 0; String font_name; String style_name; + int weight = 400; + int stretch = 100; HashMap<Vector2i, FontForSizeFallback *, VariantHasher, VariantComparator> cache; @@ -321,6 +325,58 @@ class TextServerFallback : public TextServerExtension { } } + _FORCE_INLINE_ int _font_get_weight_by_name(const String &p_sty_name) const { + String sty_name = p_sty_name.replace(" ", "").replace("-", ""); + if (sty_name.find("thin") >= 0 || sty_name.find("hairline") >= 0) { + return 100; + } else if (sty_name.find("extralight") >= 0 || sty_name.find("ultralight") >= 0) { + return 200; + } else if (sty_name.find("light") >= 0) { + return 300; + } else if (sty_name.find("semilight") >= 0) { + return 350; + } else if (sty_name.find("regular") >= 0) { + return 400; + } else if (sty_name.find("medium") >= 0) { + return 500; + } else if (sty_name.find("semibold") >= 0 || sty_name.find("demibold") >= 0) { + return 600; + } else if (sty_name.find("bold") >= 0) { + return 700; + } else if (sty_name.find("extrabold") >= 0 || sty_name.find("ultrabold") >= 0) { + return 800; + } else if (sty_name.find("black") >= 0 || sty_name.find("heavy") >= 0) { + return 900; + } else if (sty_name.find("extrablack") >= 0 || sty_name.find("ultrablack") >= 0) { + return 950; + } + return 400; + } + _FORCE_INLINE_ int _font_get_stretch_by_name(const String &p_sty_name) const { + String sty_name = p_sty_name.replace(" ", "").replace("-", ""); + if (sty_name.find("ultracondensed") >= 0) { + return 50; + } else if (sty_name.find("extracondensed") >= 0) { + return 63; + } else if (sty_name.find("condensed") >= 0) { + return 75; + } else if (sty_name.find("semicondensed") >= 0) { + return 87; + } else if (sty_name.find("semiexpanded") >= 0) { + return 113; + } else if (sty_name.find("expanded") >= 0) { + return 125; + } else if (sty_name.find("extraexpanded") >= 0) { + return 150; + } else if (sty_name.find("ultraexpanded") >= 0) { + return 200; + } + return 100; + } + _FORCE_INLINE_ bool _is_ital_style(const String &p_sty_name) const { + return (p_sty_name.find("italic") >= 0) || (p_sty_name.find("oblique") >= 0); + } + // Shaped text cache data. struct TrimData { int trim_pos = -1; @@ -397,6 +453,81 @@ class TextServerFallback : public TextServerExtension { mutable RID_PtrOwner<FontFallback> font_owner; mutable RID_PtrOwner<ShapedTextDataFallback> shaped_owner; + struct SystemFontKey { + String font_name; + TextServer::FontAntialiasing antialiasing = TextServer::FONT_ANTIALIASING_GRAY; + bool italic = false; + bool mipmaps = false; + bool msdf = false; + bool force_autohinter = false; + int weight = 400; + int stretch = 100; + int msdf_range = 14; + int msdf_source_size = 48; + int fixed_size = 0; + TextServer::Hinting hinting = TextServer::HINTING_LIGHT; + TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; + Dictionary variation_coordinates; + double oversampling = 0.0; + double embolden = 0.0; + Transform2D transform; + + bool operator==(const SystemFontKey &p_b) const { + return (font_name == p_b.font_name) && (antialiasing == p_b.antialiasing) && (italic == p_b.italic) && (mipmaps == p_b.mipmaps) && (msdf == p_b.msdf) && (force_autohinter == p_b.force_autohinter) && (weight == p_b.weight) && (stretch == p_b.stretch) && (msdf_range == p_b.msdf_range) && (msdf_source_size == p_b.msdf_source_size) && (fixed_size == p_b.fixed_size) && (hinting == p_b.hinting) && (subpixel_positioning == p_b.subpixel_positioning) && (variation_coordinates == p_b.variation_coordinates) && (oversampling == p_b.oversampling) && (embolden == p_b.embolden) && (transform == p_b.transform); + } + + SystemFontKey(const String &p_font_name, bool p_italic, int p_weight, int p_stretch, RID p_font, const TextServerFallback *p_fb) { + font_name = p_font_name; + italic = p_italic; + weight = p_weight; + stretch = p_stretch; + antialiasing = p_fb->_font_get_antialiasing(p_font); + mipmaps = p_fb->_font_get_generate_mipmaps(p_font); + msdf = p_fb->_font_is_multichannel_signed_distance_field(p_font); + msdf_range = p_fb->_font_get_msdf_pixel_range(p_font); + msdf_source_size = p_fb->_font_get_msdf_size(p_font); + fixed_size = p_fb->_font_get_fixed_size(p_font); + force_autohinter = p_fb->_font_is_force_autohinter(p_font); + hinting = p_fb->_font_get_hinting(p_font); + subpixel_positioning = p_fb->_font_get_subpixel_positioning(p_font); + variation_coordinates = p_fb->_font_get_variation_coordinates(p_font); + oversampling = p_fb->_font_get_oversampling(p_font); + embolden = p_fb->_font_get_embolden(p_font); + transform = p_fb->_font_get_transform(p_font); + } + }; + + struct SystemFontCacheRec { + RID rid; + int index = 0; + }; + + struct SystemFontCache { + Vector<SystemFontCacheRec> var; + int max_var = 0; + }; + + struct SystemFontKeyHasher { + _FORCE_INLINE_ static uint32_t hash(const SystemFontKey &p_a) { + uint32_t hash = p_a.font_name.hash(); + hash = hash_murmur3_one_32(p_a.variation_coordinates.hash(), hash); + hash = hash_murmur3_one_32(p_a.weight, hash); + hash = hash_murmur3_one_32(p_a.stretch, hash); + hash = hash_murmur3_one_32(p_a.msdf_range, hash); + hash = hash_murmur3_one_32(p_a.msdf_source_size, hash); + hash = hash_murmur3_one_32(p_a.fixed_size, hash); + hash = hash_murmur3_one_double(p_a.oversampling, hash); + hash = hash_murmur3_one_double(p_a.embolden, hash); + hash = hash_murmur3_one_real(p_a.transform[0].x, hash); + hash = hash_murmur3_one_real(p_a.transform[0].y, hash); + hash = hash_murmur3_one_real(p_a.transform[1].x, hash); + hash = hash_murmur3_one_real(p_a.transform[1].y, hash); + return hash_fmix32(hash_murmur3_one_32(((int)p_a.mipmaps) | ((int)p_a.msdf << 1) | ((int)p_a.italic << 2) | ((int)p_a.force_autohinter << 3) | ((int)p_a.hinting << 4) | ((int)p_a.subpixel_positioning << 8) | ((int)p_a.antialiasing << 12), hash)); + } + }; + mutable HashMap<SystemFontKey, SystemFontCache, SystemFontKeyHasher> system_fonts; + mutable HashMap<String, PackedByteArray> system_font_data; + void _realign(ShapedTextDataFallback *p_sd) const; protected: @@ -441,6 +572,12 @@ public: MODBIND2(font_set_style_name, const RID &, const String &); MODBIND1RC(String, font_get_style_name, const RID &); + MODBIND2(font_set_weight, const RID &, int64_t); + MODBIND1RC(int64_t, font_get_weight, const RID &); + + MODBIND2(font_set_stretch, const RID &, int64_t); + MODBIND1RC(int64_t, font_get_stretch, const RID &); + MODBIND2(font_set_name, const RID &, const String &); MODBIND1RC(String, font_get_name, const RID &); @@ -462,6 +599,9 @@ public: MODBIND2(font_set_fixed_size, const RID &, int64_t); MODBIND1RC(int64_t, font_get_fixed_size, const RID &); + MODBIND2(font_set_allow_system_fallback, const RID &, bool); + MODBIND1RC(bool, font_is_allow_system_fallback, const RID &); + MODBIND2(font_set_force_autohinter, const RID &, bool); MODBIND1RC(bool, font_is_force_autohinter, const RID &); @@ -650,6 +790,8 @@ public: MODBIND2RC(String, string_to_upper, const String &, const String &); MODBIND2RC(String, string_to_lower, const String &, const String &); + MODBIND0(cleanup); + TextServerFallback(); ~TextServerFallback(); }; diff --git a/modules/text_server_fb/thorvg_bounds_iterator.cpp b/modules/text_server_fb/thorvg_bounds_iterator.cpp new file mode 100644 index 0000000000..54a6136134 --- /dev/null +++ b/modules/text_server_fb/thorvg_bounds_iterator.cpp @@ -0,0 +1,70 @@ +/*************************************************************************/ +/* thorvg_bounds_iterator.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifdef GDEXTENSION +// Headers for building as GDExtension plug-in. + +#include <godot_cpp/godot.hpp> + +using namespace godot; + +#else +// Headers for building as built-in module. + +#include "core/typedefs.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#endif + +#ifdef MODULE_SVG_ENABLED + +#include "thorvg_bounds_iterator.h" + +#include <tvgIteratorAccessor.h> +#include <tvgPaint.h> + +// This function uses private ThorVG API to get bounding box of top level children elements. + +void tvg_get_bounds(tvg::Picture *p_picture, float &r_min_x, float &r_min_y, float &r_max_x, float &r_max_y) { + tvg::IteratorAccessor itrAccessor; + if (tvg::Iterator *it = itrAccessor.iterator(p_picture)) { + while (const tvg::Paint *child = it->next()) { + float x = 0, y = 0, w = 0, h = 0; + child->bounds(&x, &y, &w, &h, true); + r_min_x = MIN(x, r_min_x); + r_min_y = MIN(y, r_min_y); + r_max_x = MAX(x + w, r_max_x); + r_max_y = MAX(y + h, r_max_y); + } + delete (it); + } +} + +#endif // MODULE_SVG_ENABLED diff --git a/modules/text_server_fb/thorvg_bounds_iterator.h b/modules/text_server_fb/thorvg_bounds_iterator.h new file mode 100644 index 0000000000..e54e30eaa2 --- /dev/null +++ b/modules/text_server_fb/thorvg_bounds_iterator.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* thorvg_bounds_iterator.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef THORVG_BOUNDS_ITERATOR_H +#define THORVG_BOUNDS_ITERATOR_H + +#ifdef GDEXTENSION +// Headers for building as GDExtension plug-in. + +#include <godot_cpp/core/mutex_lock.hpp> +#include <godot_cpp/godot.hpp> + +using namespace godot; + +#else +// Headers for building as built-in module. + +#include "core/typedefs.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#endif + +#ifdef MODULE_SVG_ENABLED + +#include <thorvg.h> + +void tvg_get_bounds(tvg::Picture *p_picture, float &r_min_x, float &r_min_y, float &r_max_x, float &r_max_y); + +#endif // MODULE_SVG_ENABLED + +#endif // THORVG_BOUNDS_ITERATOR_H diff --git a/modules/text_server_fb/thorvg_svg_in_ot.cpp b/modules/text_server_fb/thorvg_svg_in_ot.cpp new file mode 100644 index 0000000000..7863ab67fa --- /dev/null +++ b/modules/text_server_fb/thorvg_svg_in_ot.cpp @@ -0,0 +1,320 @@ +/*************************************************************************/ +/* thorvg_svg_in_ot.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifdef GDEXTENSION +// Headers for building as GDExtension plug-in. + +#include <godot_cpp/classes/xml_parser.hpp> +#include <godot_cpp/core/mutex_lock.hpp> +#include <godot_cpp/godot.hpp> +#include <godot_cpp/templates/vector.hpp> + +using namespace godot; + +#else +// Headers for building as built-in module. + +#include "core/error/error_macros.h" +#include "core/io/xml_parser.h" +#include "core/os/memory.h" +#include "core/os/os.h" +#include "core/string/ustring.h" +#include "core/typedefs.h" +#include "core/variant/variant.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#endif + +#ifdef MODULE_SVG_ENABLED + +#include "thorvg_bounds_iterator.h" +#include "thorvg_svg_in_ot.h" + +#include <freetype/otsvg.h> +#include <ft2build.h> + +#include <math.h> +#include <stdlib.h> + +FT_Error tvg_svg_in_ot_init(FT_Pointer *p_state) { + *p_state = memnew(TVG_State); + + return FT_Err_Ok; +} + +void tvg_svg_in_ot_free(FT_Pointer *p_state) { + TVG_State *state = *reinterpret_cast<TVG_State **>(p_state); + memdelete(state); +} + +FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Pointer *p_state) { + TVG_State *state = *reinterpret_cast<TVG_State **>(p_state); + if (!state) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG in OT state not initialized."); + } + MutexLock lock(state->mutex); + + FT_SVG_Document document = (FT_SVG_Document)p_slot->other; + FT_Size_Metrics metrics = document->metrics; + + GL_State &gl_state = state->glyph_map[p_slot->glyph_index]; + if (!gl_state.ready) { + Ref<XMLParser> parser; + parser.instantiate(); +#ifdef GDEXTENSION + PackedByteArray data; + data.resize(document->svg_document_length); + memcpy(data.ptrw(), document->svg_document, document->svg_document_length); + parser->open_buffer(data); +#else + parser->_open_buffer((const uint8_t *)document->svg_document, document->svg_document_length); +#endif + + float aspect = 1.0f; + String xml_body; + while (parser->read() == OK) { + if (parser->has_attribute("id")) { +#ifdef GDEXTENSION + const String &gl_name = parser->get_named_attribute_value("id"); +#else + const String &gl_name = parser->get_attribute_value("id"); +#endif + if (gl_name.begins_with("glyph")) { + int dot_pos = gl_name.find("."); + int64_t gl_idx = gl_name.substr(5, (dot_pos > 0) ? dot_pos - 5 : -1).to_int(); + if (p_slot->glyph_index != gl_idx) { + parser->skip_section(); + continue; + } + } + } + if (parser->get_node_type() == XMLParser::NODE_ELEMENT && parser->get_node_name() == "svg") { + if (parser->has_attribute("viewBox")) { +#ifdef GDEXTENSION + PackedStringArray vb = parser->get_named_attribute_value("viewBox").split(" "); +#else + Vector<String> vb = parser->get_attribute_value("viewBox").split(" "); +#endif + + if (vb.size() == 4) { + aspect = vb[2].to_float() / vb[3].to_float(); + } + } + continue; + } +#ifdef GDEXTENSION + if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { + xml_body = xml_body + "<" + parser->get_node_name(); + for (int i = 0; i < parser->get_attribute_count(); i++) { + xml_body = xml_body + " " + parser->get_attribute_name(i) + "=\"" + parser->get_attribute_value(i) + "\""; + } + xml_body = xml_body + ">"; + } else if (parser->get_node_type() == XMLParser::NODE_TEXT) { + xml_body = xml_body + parser->get_node_data(); + } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) { + xml_body = xml_body + "</" + parser->get_node_name() + ">"; + } +#else + if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { + xml_body += vformat("<%s", parser->get_node_name()); + for (int i = 0; i < parser->get_attribute_count(); i++) { + xml_body += vformat(" %s=\"%s\"", parser->get_attribute_name(i), parser->get_attribute_value(i)); + } + xml_body += ">"; + } else if (parser->get_node_type() == XMLParser::NODE_TEXT) { + xml_body += parser->get_node_data(); + } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) { + xml_body += vformat("</%s>", parser->get_node_name()); + } +#endif + } + String temp_xml = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 0 0\">" + xml_body; + + std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen(); + tvg::Result result = picture->load(temp_xml.utf8().get_data(), temp_xml.utf8().length(), "svg+xml", false); + if (result != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (bounds detection)."); + } + + float min_x = INFINITY, min_y = INFINITY, max_x = -INFINITY, max_y = -INFINITY; + tvg_get_bounds(picture.get(), min_x, min_y, max_x, max_y); + + float new_h = (max_y - min_y); + float new_w = (max_x - min_x); + + if (new_h * aspect >= new_w) { + new_w = (new_h * aspect); + } else { + new_h = (new_w / aspect); + } + +#ifdef GDEXTENSION + gl_state.xml_code = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"" + rtos(min_x) + " " + rtos(min_y) + " " + rtos(new_w) + " " + rtos(new_h) + "\">" + xml_body; +#else + gl_state.xml_code = vformat("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"%f %f %f %f\">", min_x, min_y, new_w, new_h) + xml_body; +#endif + + picture = tvg::Picture::gen(); + result = picture->load(gl_state.xml_code.utf8().get_data(), gl_state.xml_code.utf8().length(), "svg+xml", false); + if (result != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph metrics)."); + } + + float x_svg_to_out, y_svg_to_out; + x_svg_to_out = (float)metrics.x_ppem / new_w; + y_svg_to_out = (float)metrics.y_ppem / new_h; + + gl_state.m.e11 = (double)document->transform.xx / (1 << 16) * x_svg_to_out; + gl_state.m.e12 = -(double)document->transform.xy / (1 << 16) * x_svg_to_out; + gl_state.m.e21 = -(double)document->transform.yx / (1 << 16) * y_svg_to_out; + gl_state.m.e22 = (double)document->transform.yy / (1 << 16) * y_svg_to_out; + gl_state.m.e13 = (double)document->delta.x / 64 * new_w / metrics.x_ppem; + gl_state.m.e23 = -(double)document->delta.y / 64 * new_h / metrics.y_ppem; + gl_state.m.e31 = 0; + gl_state.m.e32 = 0; + gl_state.m.e33 = 1; + + result = picture->transform(gl_state.m); + if (result != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to apply transform to SVG document."); + } + + result = picture->bounds(&gl_state.x, &gl_state.y, &gl_state.w, &gl_state.h, true); + if (result != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to get SVG bounds."); + } + + gl_state.bmp_y = -min_y * gl_state.h / new_h; + gl_state.bmp_x = min_x * gl_state.w / new_w; + + gl_state.ready = true; + } + + p_slot->bitmap_left = (FT_Int)gl_state.bmp_x; + p_slot->bitmap_top = (FT_Int)gl_state.bmp_y; + + float tmp = ceil(gl_state.h); + p_slot->bitmap.rows = (unsigned int)tmp; + tmp = ceil(gl_state.w); + p_slot->bitmap.width = (unsigned int)tmp; + p_slot->bitmap.pitch = (int)p_slot->bitmap.width * 4; + p_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA; + + float metrics_width, metrics_height; + float horiBearingX, horiBearingY; + float vertBearingX, vertBearingY; + + metrics_width = (float)gl_state.w; + metrics_height = (float)gl_state.h; + horiBearingX = (float)gl_state.x; + horiBearingY = (float)-gl_state.y; + vertBearingX = p_slot->metrics.horiBearingX / 64.0f - p_slot->metrics.horiAdvance / 64.0f / 2; + vertBearingY = (p_slot->metrics.vertAdvance / 64.0f - p_slot->metrics.height / 64.0f) / 2; + + tmp = roundf(metrics_width * 64); + p_slot->metrics.width = (FT_Pos)tmp; + tmp = roundf(metrics_height * 64); + p_slot->metrics.height = (FT_Pos)tmp; + + p_slot->metrics.horiBearingX = (FT_Pos)(horiBearingX * 64); + p_slot->metrics.horiBearingY = (FT_Pos)(horiBearingY * 64); + p_slot->metrics.vertBearingX = (FT_Pos)(vertBearingX * 64); + p_slot->metrics.vertBearingY = (FT_Pos)(vertBearingY * 64); + + if (p_slot->metrics.vertAdvance == 0) { + p_slot->metrics.vertAdvance = (FT_Pos)(metrics_height * 1.2f * 64); + } + + return FT_Err_Ok; +} + +FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state) { + TVG_State *state = *reinterpret_cast<TVG_State **>(p_state); + if (!state) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG in OT state not initialized."); + } + MutexLock lock(state->mutex); + + if (!state->glyph_map.has(p_slot->glyph_index)) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG glyph not loaded."); + } + + GL_State &gl_state = state->glyph_map[p_slot->glyph_index]; + ERR_FAIL_COND_V_MSG(!gl_state.ready, FT_Err_Invalid_SVG_Document, "SVG glyph not ready."); + + std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen(); + tvg::Result res = picture->load(gl_state.xml_code.utf8().get_data(), gl_state.xml_code.utf8().length(), "svg+xml", false); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph rendering)."); + } + res = picture->transform(gl_state.m); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to apply transform to SVG document."); + } + + std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen(); + res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888_STRAIGHT); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to create SVG canvas."); + } + res = sw_canvas->push(std::move(picture)); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to set SVG canvas source."); + } + res = sw_canvas->draw(); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to draw to SVG canvas."); + } + res = sw_canvas->sync(); + if (res != tvg::Result::Success) { + ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to sync SVG canvas."); + } + + state->glyph_map.erase(p_slot->glyph_index); + + p_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA; + p_slot->bitmap.num_grays = 256; + p_slot->format = FT_GLYPH_FORMAT_BITMAP; + + return FT_Err_Ok; +} + +SVG_RendererHooks tvg_svg_in_ot_hooks = { + (SVG_Lib_Init_Func)tvg_svg_in_ot_init, + (SVG_Lib_Free_Func)tvg_svg_in_ot_free, + (SVG_Lib_Render_Func)tvg_svg_in_ot_render, + (SVG_Lib_Preset_Slot_Func)tvg_svg_in_ot_preset_slot, +}; + +SVG_RendererHooks *get_tvg_svg_in_ot_hooks() { + return &tvg_svg_in_ot_hooks; +} + +#endif // MODULE_SVG_ENABLED diff --git a/modules/text_server_fb/thorvg_svg_in_ot.h b/modules/text_server_fb/thorvg_svg_in_ot.h new file mode 100644 index 0000000000..b2816193d9 --- /dev/null +++ b/modules/text_server_fb/thorvg_svg_in_ot.h @@ -0,0 +1,86 @@ +/*************************************************************************/ +/* thorvg_svg_in_ot.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef THORVG_SVG_IN_OT_H +#define THORVG_SVG_IN_OT_H + +#ifdef GDEXTENSION +// Headers for building as GDExtension plug-in. + +#include <godot_cpp/core/mutex_lock.hpp> +#include <godot_cpp/godot.hpp> +#include <godot_cpp/templates/hash_map.hpp> + +using namespace godot; + +#else +// Headers for building as built-in module. + +#include "core/os/mutex.h" +#include "core/templates/hash_map.h" +#include "core/typedefs.h" + +#include "modules/modules_enabled.gen.h" // For svg. +#endif + +#ifdef MODULE_SVG_ENABLED + +#include <freetype/freetype.h> +#include <freetype/otsvg.h> +#include <ft2build.h> +#include <thorvg.h> + +struct GL_State { + bool ready = false; + float bmp_x = 0; + float bmp_y = 0; + float x = 0; + float y = 0; + float w = 0; + float h = 0; + String xml_code; + tvg::Matrix m; +}; + +struct TVG_State { + Mutex mutex; + HashMap<uint32_t, GL_State> glyph_map; +}; + +FT_Error tvg_svg_in_ot_init(FT_Pointer *p_state); +void tvg_svg_in_ot_free(FT_Pointer *p_state); +FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Pointer *p_state); +FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state); + +SVG_RendererHooks *get_tvg_svg_in_ot_hooks(); + +#endif // MODULE_SVG_ENABLED + +#endif // THORVG_SVG_IN_OT_H diff --git a/modules/theora/SCsub b/modules/theora/SCsub index 6038ea086a..ca666050dd 100644 --- a/modules/theora/SCsub +++ b/modules/theora/SCsub @@ -15,7 +15,7 @@ if env["builtin_libtheora"]: # "analyze.c", # "apiwrapper.c", "bitpack.c", - "cpu.c", + # "collect.c", # "decapiwrapper.c", "decinfo.c", "decode.c", @@ -47,8 +47,12 @@ if env["builtin_libtheora"]: "x86/mmxfrag.c", "x86/mmxidct.c", "x86/mmxstate.c", + # "x86/sse2encfrag.c", # "x86/sse2fdct.c", + "x86/sse2idct.c", + "x86/x86cpu.c", # "x86/x86enc.c", + # "x86/x86enquant.c" "x86/x86state.c", ] @@ -58,6 +62,7 @@ if env["builtin_libtheora"]: "x86_vc/mmxfrag.c", "x86_vc/mmxidct.c", "x86_vc/mmxstate.c", + "x86_vc/x86cpu.c", # "x86_vc/x86enc.c", "x86_vc/x86state.c", ] diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp index 792103cd31..63909257d9 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp @@ -438,9 +438,7 @@ void AudioStreamOggVorbis::maybe_update_info() { } if (i == 0) { packet->b_o_s = 1; - } - if (i == 0) { ERR_FAIL_COND(!vorbis_synthesis_idheader(packet)); } diff --git a/modules/webp/webp_common.cpp b/modules/webp/webp_common.cpp index af98788420..572a33653e 100644 --- a/modules/webp/webp_common.cpp +++ b/modules/webp/webp_common.cpp @@ -41,40 +41,21 @@ namespace WebPCommon { Vector<uint8_t> _webp_lossy_pack(const Ref<Image> &p_image, float p_quality) { ERR_FAIL_COND_V(p_image.is_null() || p_image->is_empty(), Vector<uint8_t>()); - Ref<Image> img = p_image->duplicate(); - if (img->detect_alpha()) { - img->convert(Image::FORMAT_RGBA8); - } else { - img->convert(Image::FORMAT_RGB8); - } - - Size2 s(img->get_width(), img->get_height()); - Vector<uint8_t> data = img->get_data(); - const uint8_t *r = data.ptr(); - - uint8_t *dst_buff = nullptr; - size_t dst_size = 0; - if (img->get_format() == Image::FORMAT_RGB8) { - dst_size = WebPEncodeRGB(r, s.width, s.height, 3 * s.width, CLAMP(p_quality * 100.0f, 0.0f, 100.0f), &dst_buff); - } else { - dst_size = WebPEncodeRGBA(r, s.width, s.height, 4 * s.width, CLAMP(p_quality * 100.0f, 0.0f, 100.0f), &dst_buff); - } - - ERR_FAIL_COND_V(dst_size == 0, Vector<uint8_t>()); - Vector<uint8_t> dst; - dst.resize(dst_size); - uint8_t *w = dst.ptrw(); - memcpy(w, dst_buff, dst_size); - WebPFree(dst_buff); - - return dst; + return _webp_packer(p_image, CLAMP(p_quality * 100.0f, 0.0f, 100.0f), false); } Vector<uint8_t> _webp_lossless_pack(const Ref<Image> &p_image) { ERR_FAIL_COND_V(p_image.is_null() || p_image->is_empty(), Vector<uint8_t>()); - int compression_level = GLOBAL_GET("rendering/textures/lossless_compression/webp_compression_level"); - compression_level = CLAMP(compression_level, 0, 9); + float compression_factor = GLOBAL_GET("rendering/textures/webp_compression/lossless_compression_factor"); + compression_factor = CLAMP(compression_factor, 0.0f, 100.0f); + + return _webp_packer(p_image, compression_factor, true); +} + +Vector<uint8_t> _webp_packer(const Ref<Image> &p_image, float p_quality, bool p_lossless) { + int compression_method = GLOBAL_GET("rendering/textures/webp_compression/compression_method"); + compression_method = CLAMP(compression_method, 0, 6); Ref<Image> img = p_image->duplicate(); if (img->detect_alpha()) { @@ -87,16 +68,21 @@ Vector<uint8_t> _webp_lossless_pack(const Ref<Image> &p_image) { Vector<uint8_t> data = img->get_data(); const uint8_t *r = data.ptr(); - // we need to use the more complex API in order to access the 'exact' flag... + // we need to use the more complex API in order to access specific flags... WebPConfig config; WebPPicture pic; - if (!WebPConfigInit(&config) || !WebPConfigLosslessPreset(&config, compression_level) || !WebPPictureInit(&pic)) { + if (!WebPConfigInit(&config) || !WebPPictureInit(&pic)) { ERR_FAIL_V(Vector<uint8_t>()); } WebPMemoryWriter wrt; - config.exact = 1; + if (p_lossless) { + config.lossless = 1; + config.exact = 1; + } + config.method = compression_method; + config.quality = p_quality; pic.use_argb = 1; pic.width = s.width; pic.height = s.height; diff --git a/modules/webp/webp_common.h b/modules/webp/webp_common.h index 11bef40256..23b433ad79 100644 --- a/modules/webp/webp_common.h +++ b/modules/webp/webp_common.h @@ -37,6 +37,8 @@ namespace WebPCommon { // Given an image, pack this data into a WebP file. Vector<uint8_t> _webp_lossy_pack(const Ref<Image> &p_image, float p_quality); Vector<uint8_t> _webp_lossless_pack(const Ref<Image> &p_image); +// Helper function for those above. +Vector<uint8_t> _webp_packer(const Ref<Image> &p_image, float p_quality, bool p_lossless); // Given a WebP file, unpack it into an image. Ref<Image> _webp_unpack(const Vector<uint8_t> &p_buffer); Error webp_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p_buffer_len); diff --git a/modules/webrtc/doc_classes/WebRTCDataChannel.xml b/modules/webrtc/doc_classes/WebRTCDataChannel.xml index a9ba8a23de..a186631ca8 100644 --- a/modules/webrtc/doc_classes/WebRTCDataChannel.xml +++ b/modules/webrtc/doc_classes/WebRTCDataChannel.xml @@ -22,8 +22,8 @@ <method name="get_id" qualifiers="const"> <return type="int" /> <description> - Returns the id assigned to this channel during creation (or auto-assigned during negotiation). - If the channel is not negotiated out-of-band the id will only be available after the connection is established (will return [code]65535[/code] until then). + Returns the ID assigned to this channel during creation (or auto-assigned during negotiation). + If the channel is not negotiated out-of-band the ID will only be available after the connection is established (will return [code]65535[/code] until then). </description> </method> <method name="get_label" qualifiers="const"> diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml index 49dd9f7318..f5964eb4d1 100644 --- a/modules/webxr/doc_classes/WebXRInterface.xml +++ b/modules/webxr/doc_classes/WebXRInterface.xml @@ -1,13 +1,13 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="WebXRInterface" inherits="XRInterface" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - AR/VR interface using WebXR. + XR interface using WebXR. </brief_description> <description> WebXR is an open standard that allows creating VR and AR applications that run in the web browser. As such, this interface is only available when running in Web exports. WebXR supports a wide range of devices, from the very capable (like Valve Index, HTC Vive, Oculus Rift and Quest) down to the much less capable (like Google Cardboard, Oculus Go, GearVR, or plain smartphones). - Since WebXR is based on JavaScript, it makes extensive use of callbacks, which means that [WebXRInterface] is forced to use signals, where other AR/VR interfaces would instead use functions that return a result immediately. This makes [WebXRInterface] quite a bit more complicated to initialize than other AR/VR interfaces. + Since WebXR is based on JavaScript, it makes extensive use of callbacks, which means that [WebXRInterface] is forced to use signals, where other XR interfaces would instead use functions that return a result immediately. This makes [WebXRInterface] quite a bit more complicated to initialize than other XR interfaces. Here's the minimum code required to start an immersive VR session: [codeblock] extends Node3D @@ -69,7 +69,7 @@ func _webxr_session_started(): $Button.visible = false # This tells Godot to start rendering to the headset. - get_viewport().xr = true + get_viewport().use_xr = true # This will be the reference space type you ultimately got, out of the # types that you requested above. This is useful if you want the game to # work a little differently in 'bounded-floor' versus 'local-floor'. @@ -79,28 +79,35 @@ $Button.visible = true # If the user exits immersive mode, then we tell Godot to render to the web # page again. - get_viewport().xr = false + get_viewport().use_xr = false func _webxr_session_failed(message): OS.alert("Failed to initialize: " + message) [/codeblock] - There are several ways to handle "controller" input: - - Using [XRController3D] nodes and their [signal XRController3D.button_pressed] and [signal XRController3D.button_released] signals. This is how controllers are typically handled in AR/VR apps in Godot, however, this will only work with advanced VR controllers like the Oculus Touch or Index controllers, for example. The buttons codes are defined by [url=https://immersive-web.github.io/webxr-gamepads-module/#xr-standard-gamepad-mapping]Section 3.3 of the WebXR Gamepads Module[/url]. - - Using [method Node._unhandled_input] and [InputEventJoypadButton] or [InputEventJoypadMotion]. This works the same as normal joypads, except the [member InputEvent.device] starts at 100, so the left controller is 100 and the right controller is 101, and the button codes are also defined by [url=https://immersive-web.github.io/webxr-gamepads-module/#xr-standard-gamepad-mapping]Section 3.3 of the WebXR Gamepads Module[/url]. - - Using the [signal select], [signal squeeze] and related signals. This method will work for both advanced VR controllers, and non-traditional "controllers" like a tap on the screen, a spoken voice command or a button press on the device itself. - You can use one or all of these methods to allow your game or app to support a wider or narrower set of devices and input methods, or to allow more advanced interactions with more advanced devices. + There are a couple ways to handle "controller" input: + - Using [XRController3D] nodes and their [signal XRController3D.button_pressed] and [signal XRController3D.button_released] signals. This is how controllers are typically handled in XR apps in Godot, however, this will only work with advanced VR controllers like the Oculus Touch or Index controllers, for example. + - Using the [signal select], [signal squeeze] and related signals. This method will work for both advanced VR controllers, and non-traditional input sources like a tap on the screen, a spoken voice command or a button press on the device itself. + You can use both methods to allow your game or app to support a wider or narrower set of devices and input methods, or to allow more advanced interactions with more advanced devices. </description> <tutorials> <link title="How to make a VR game for WebXR with Godot">https://www.snopekgames.com/blog/2020/how-make-vr-game-webxr-godot</link> </tutorials> <methods> - <method name="get_controller" qualifiers="const"> + <method name="get_input_source_target_ray_mode" qualifiers="const"> + <return type="int" enum="WebXRInterface.TargetRayMode" /> + <param index="0" name="input_source_id" type="int" /> + <description> + Returns the target ray mode for the given [code]input_source_id[/code]. + This can help interpret the input coming from that input source. See [url=https://developer.mozilla.org/en-US/docs/Web/API/XRInputSource/targetRayMode]XRInputSource.targetRayMode[/url] for more information. + </description> + </method> + <method name="get_input_source_tracker" qualifiers="const"> <return type="XRPositionalTracker" /> - <param index="0" name="controller_id" type="int" /> + <param index="0" name="input_source_id" type="int" /> <description> - Gets an [XRPositionalTracker] for the given [code]controller_id[/code]. - In the context of WebXR, a "controller" can be an advanced VR controller like the Oculus Touch or Index controllers, or even a tap on the screen, a spoken voice command or a button press on the device itself. When a non-traditional controller is used, interpret the position and orientation of the [XRPositionalTracker] as a ray pointing at the object the user wishes to interact with. - Use this method to get information about the controller that triggered one of these signals: + Gets an [XRPositionalTracker] for the given [code]input_source_id[/code]. + In the context of WebXR, an input source can be an advanced VR controller like the Oculus Touch or Index controllers, or even a tap on the screen, a spoken voice command or a button press on the device itself. When a non-traditional input source is used, interpret the position and orientation of the [XRPositionalTracker] as a ray pointing at the object the user wishes to interact with. + Use this method to get information about the input source that triggered one of these signals: - [signal selectstart] - [signal select] - [signal selectend] @@ -109,6 +116,13 @@ - [signal squeezestart] </description> </method> + <method name="is_input_source_active" qualifiers="const"> + <return type="bool" /> + <param index="0" name="input_source_id" type="int" /> + <description> + Returns [code]true[/code] if there is an active input source with the given [code]input_source_id[/code]. + </description> + </method> <method name="is_session_supported"> <return type="void" /> <param index="0" name="session_mode" type="String" /> @@ -120,11 +134,6 @@ </method> </methods> <members> - <member name="bounds_geometry" type="PackedVector3Array" setter="" getter="get_bounds_geometry"> - The vertices of a polygon which defines the boundaries of the user's play area. - This will only be available if [member reference_space_type] is [code]"bounded-floor"[/code] and only on certain browsers and devices that support it. - The [signal reference_space_reset] signal may indicate when this changes. - </member> <member name="optional_features" type="String" setter="set_optional_features" getter="get_optional_features"> A comma-seperated list of optional features used by [method XRInterface.initialize] when setting up the WebXR session. If a user's browser or device doesn't support one of the given features, initialization will continue, but you won't be able to use the requested feature. @@ -137,7 +146,7 @@ </member> <member name="requested_reference_space_types" type="String" setter="set_requested_reference_space_types" getter="get_requested_reference_space_types"> A comma-seperated list of reference space types used by [method XRInterface.initialize] when setting up the WebXR session. - The reference space types are requested in order, and the first on supported by the users device or browser will be used. The [member reference_space_type] property contains the reference space type that was ultimately used. + The reference space types are requested in order, and the first one supported by the users device or browser will be used. The [member reference_space_type] property contains the reference space type that was ultimately selected. This doesn't have any effect on the interface when already initialized. Possible values come from [url=https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpaceType]WebXR's XRReferenceSpaceType[/url]. If you want to use a particular reference space type, it must be listed in either [member required_features] or [member optional_features]. </member> @@ -161,35 +170,35 @@ <signal name="reference_space_reset"> <description> Emitted to indicate that the reference space has been reset or reconfigured. - When (or whether) this is emitted depends on the user's browser or device, but may include when the user has changed the dimensions of their play space (which you may be able to access via [member bounds_geometry]) or pressed/held a button to recenter their position. + When (or whether) this is emitted depends on the user's browser or device, but may include when the user has changed the dimensions of their play space (which you may be able to access via [method XRInterface.get_play_area]) or pressed/held a button to recenter their position. See [url=https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpace/reset_event]WebXR's XRReferenceSpace reset event[/url] for more information. </description> </signal> <signal name="select"> - <param index="0" name="controller_id" type="int" /> + <param index="0" name="input_source_id" type="int" /> <description> - Emitted after one of the "controllers" has finished its "primary action". - Use [method get_controller] to get more information about the controller. + Emitted after one of the input sources has finished its "primary action". + Use [method get_input_source_tracker] and [method get_input_source_target_ray_mode] to get more information about the input source. </description> </signal> <signal name="selectend"> - <param index="0" name="controller_id" type="int" /> + <param index="0" name="input_source_id" type="int" /> <description> - Emitted when one of the "controllers" has finished its "primary action". - Use [method get_controller] to get more information about the controller. + Emitted when one of the input sources has finished its "primary action". + Use [method get_input_source_tracker] and [method get_input_source_target_ray_mode] to get more information about the input source. </description> </signal> <signal name="selectstart"> - <param index="0" name="controller_id" type="int" /> + <param index="0" name="input_source_id" type="int" /> <description> - Emitted when one of the "controllers" has started its "primary action". - Use [method get_controller] to get more information about the controller. + Emitted when one of the input source has started its "primary action". + Use [method get_input_source_tracker] and [method get_input_source_target_ray_mode] to get more information about the input source. </description> </signal> <signal name="session_ended"> <description> Emitted when the user ends the WebXR session (which can be done using UI from the browser or device). - At this point, you should do [code]get_viewport().xr = false[/code] to instruct Godot to resume rendering to the screen. + At this point, you should do [code]get_viewport().use_xr = false[/code] to instruct Godot to resume rendering to the screen. </description> </signal> <signal name="session_failed"> @@ -202,7 +211,7 @@ <signal name="session_started"> <description> Emitted by [method XRInterface.initialize] if the session is successfully started. - At this point, it's safe to do [code]get_viewport().xr = true[/code] to instruct Godot to start rendering to the AR/VR device. + At this point, it's safe to do [code]get_viewport().use_xr = true[/code] to instruct Godot to start rendering to the XR device. </description> </signal> <signal name="session_supported"> @@ -213,24 +222,24 @@ </description> </signal> <signal name="squeeze"> - <param index="0" name="controller_id" type="int" /> + <param index="0" name="input_source_id" type="int" /> <description> - Emitted after one of the "controllers" has finished its "primary squeeze action". - Use [method get_controller] to get more information about the controller. + Emitted after one of the input sources has finished its "primary squeeze action". + Use [method get_input_source_tracker] and [method get_input_source_target_ray_mode] to get more information about the input source. </description> </signal> <signal name="squeezeend"> - <param index="0" name="controller_id" type="int" /> + <param index="0" name="input_source_id" type="int" /> <description> - Emitted when one of the "controllers" has finished its "primary squeeze action". - Use [method get_controller] to get more information about the controller. + Emitted when one of the input sources has finished its "primary squeeze action". + Use [method get_input_source_tracker] and [method get_input_source_target_ray_mode] to get more information about the input source. </description> </signal> <signal name="squeezestart"> - <param index="0" name="controller_id" type="int" /> + <param index="0" name="input_source_id" type="int" /> <description> - Emitted when one of the "controllers" has started its "primary squeeze action". - Use [method get_controller] to get more information about the controller. + Emitted when one of the input sources has started its "primary squeeze action". + Use [method get_input_source_tracker] and [method get_input_source_target_ray_mode] to get more information about the input source. </description> </signal> <signal name="visibility_state_changed"> @@ -239,4 +248,18 @@ </description> </signal> </signals> + <constants> + <constant name="TARGET_RAY_MODE_UNKNOWN" value="0" enum="TargetRayMode"> + We don't know the the target ray mode. + </constant> + <constant name="TARGET_RAY_MODE_GAZE" value="1" enum="TargetRayMode"> + Target ray originates at the viewer's eyes and points in the direction they are looking. + </constant> + <constant name="TARGET_RAY_MODE_TRACKED_POINTER" value="2" enum="TargetRayMode"> + Target ray from a handheld pointer, most likely a VR touch controller. + </constant> + <constant name="TARGET_RAY_MODE_SCREEN" value="3" enum="TargetRayMode"> + Target ray from touch screen, mouse or other tactile input device. + </constant> + </constants> </class> diff --git a/modules/webxr/godot_webxr.h b/modules/webxr/godot_webxr.h index d8d5bd99cc..e31a1d307e 100644 --- a/modules/webxr/godot_webxr.h +++ b/modules/webxr/godot_webxr.h @@ -37,12 +37,18 @@ extern "C" { #include "stddef.h" +enum WebXRInputEvent { + WEBXR_INPUT_EVENT_SELECTSTART, + WEBXR_INPUT_EVENT_SELECTEND, + WEBXR_INPUT_EVENT_SQUEEZESTART, + WEBXR_INPUT_EVENT_SQUEEZEEND, +}; + typedef void (*GodotWebXRSupportedCallback)(char *p_session_mode, int p_supported); typedef void (*GodotWebXRStartedCallback)(char *p_reference_space_type); typedef void (*GodotWebXREndedCallback)(); typedef void (*GodotWebXRFailedCallback)(char *p_message); -typedef void (*GodotWebXRControllerCallback)(); -typedef void (*GodotWebXRInputEventCallback)(char *p_signal_name, int p_controller_id); +typedef void (*GodotWebXRInputEventCallback)(int p_event_type, int p_input_source_id); typedef void (*GodotWebXRSimpleEventCallback)(char *p_signal_name); extern int godot_webxr_is_supported(); @@ -56,26 +62,33 @@ extern void godot_webxr_initialize( GodotWebXRStartedCallback p_on_session_started, GodotWebXREndedCallback p_on_session_ended, GodotWebXRFailedCallback p_on_session_failed, - GodotWebXRControllerCallback p_on_controller_changed, GodotWebXRInputEventCallback p_on_input_event, GodotWebXRSimpleEventCallback p_on_simple_event); extern void godot_webxr_uninitialize(); extern int godot_webxr_get_view_count(); -extern int *godot_webxr_get_render_target_size(); -extern float *godot_webxr_get_transform_for_eye(int p_eye); -extern float *godot_webxr_get_projection_for_eye(int p_eye); -extern void godot_webxr_commit(unsigned int p_texture); +extern bool godot_webxr_get_render_target_size(int *r_size); +extern bool godot_webxr_get_transform_for_view(int p_view, float *r_transform); +extern bool godot_webxr_get_projection_for_view(int p_view, float *r_transform); +extern unsigned int godot_webxr_get_color_texture(); +extern unsigned int godot_webxr_get_depth_texture(); +extern unsigned int godot_webxr_get_velocity_texture(); -extern void godot_webxr_sample_controller_data(); -extern int godot_webxr_get_controller_count(); -extern int godot_webxr_is_controller_connected(int p_controller); -extern float *godot_webxr_get_controller_transform(int p_controller); -extern int *godot_webxr_get_controller_buttons(int p_controller); -extern int *godot_webxr_get_controller_axes(int p_controller); +extern bool godot_webxr_update_input_source( + int p_input_source_id, + float *r_target_pose, + int *r_target_ray_mode, + int *r_touch_index, + int *r_has_grip_pose, + float *r_grip_pose, + int *r_has_standard_mapping, + int *r_button_count, + float *r_buttons, + int *r_axes_count, + float *r_axes); extern char *godot_webxr_get_visibility_state(); -extern int *godot_webxr_get_bounds_geometry(); +extern int godot_webxr_get_bounds_geometry(float **r_points); #ifdef __cplusplus } diff --git a/modules/webxr/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js index c476a54c59..eaf251d48f 100644 --- a/modules/webxr/native/library_godot_webxr.js +++ b/modules/webxr/native/library_godot_webxr.js @@ -28,14 +28,19 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ const GodotWebXR = { - $GodotWebXR__deps: ['$Browser', '$GL', '$GodotRuntime'], + $GodotWebXR__deps: ['$Browser', '$GL', '$GodotRuntime', '$runtimeKeepalivePush', '$runtimeKeepalivePop'], $GodotWebXR: { gl: null, session: null, + gl_binding: null, + layer: null, space: null, frame: null, pose: null, + view_count: 1, + input_sources: new Array(16), + touches: new Array(5), // Monkey-patch the requestAnimationFrame() used by Emscripten for the main // loop, so that we can swap it out for XRSession.requestAnimationFrame() @@ -69,39 +74,135 @@ const GodotWebXR = { // gets picked up automatically, however, in the Oculus Browser // on the Quest, we need to pause and resume the main loop. Browser.mainLoop.pause(); + runtimeKeepalivePush(); // eslint-disable-line no-undef window.setTimeout(function () { + runtimeKeepalivePop(); // eslint-disable-line no-undef Browser.mainLoop.resume(); }, 0); }, - // Holds the controllers list between function calls. - controllers: [], + getLayer: () => { + const new_view_count = (GodotWebXR.pose) ? GodotWebXR.pose.views.length : 1; + let layer = GodotWebXR.layer; - // Updates controllers array, where the left hand (or sole tracker) is - // the first element, and the right hand is the second element, and any - // others placed at the 3rd position and up. - sampleControllers: () => { - if (!GodotWebXR.session || !GodotWebXR.frame) { - return; + // If the view count hasn't changed since creating this layer, then + // we can simply return it. + if (layer && GodotWebXR.view_count === new_view_count) { + return layer; } - let other_index = 2; - const controllers = []; - GodotWebXR.session.inputSources.forEach((input_source) => { - if (input_source.targetRayMode === 'tracked-pointer') { - if (input_source.handedness === 'right') { - controllers[1] = input_source; - } else if (input_source.handedness === 'left' || !controllers[0]) { - controllers[0] = input_source; + if (!GodotWebXR.session || !GodotWebXR.gl_binding) { + return null; + } + + const gl = GodotWebXR.gl; + + layer = GodotWebXR.gl_binding.createProjectionLayer({ + textureType: new_view_count > 1 ? 'texture-array' : 'texture', + colorFormat: gl.RGBA8, + depthFormat: gl.DEPTH_COMPONENT24, + }); + GodotWebXR.session.updateRenderState({ layers: [layer] }); + + GodotWebXR.layer = layer; + GodotWebXR.view_count = new_view_count; + return layer; + }, + + getSubImage: () => { + if (!GodotWebXR.pose) { + return null; + } + const layer = GodotWebXR.getLayer(); + if (layer === null) { + return null; + } + + // Because we always use "texture-array" for multiview and "texture" + // when there is only 1 view, it should be safe to only grab the + // subimage for the first view. + return GodotWebXR.gl_binding.getViewSubImage(layer, GodotWebXR.pose.views[0]); + }, + + getTextureId: (texture) => { + if (texture.name !== undefined) { + return texture.name; + } + + const id = GL.getNewId(GL.textures); + texture.name = id; + GL.textures[id] = texture; + + return id; + }, + + addInputSource: (input_source) => { + let name = -1; + if (input_source.targetRayMode === 'tracked-pointer' && input_source.handedness === 'left') { + name = 0; + } else if (input_source.targetRayMode === 'tracked-pointer' && input_source.handedness === 'right') { + name = 1; + } else { + for (let i = 2; i < 16; i++) { + if (!GodotWebXR.input_sources[i]) { + name = i; + break; } - } else { - controllers[other_index++] = input_source; } - }); - GodotWebXR.controllers = controllers; + } + if (name >= 0) { + GodotWebXR.input_sources[name] = input_source; + input_source.name = name; + + // Find a free touch index for screen sources. + if (input_source.targetRayMode === 'screen') { + let touch_index = -1; + for (let i = 0; i < 5; i++) { + if (!GodotWebXR.touches[i]) { + touch_index = i; + break; + } + } + if (touch_index >= 0) { + GodotWebXR.touches[touch_index] = input_source; + input_source.touch_index = touch_index; + } + } + } + return name; }, - getControllerId: (input_source) => GodotWebXR.controllers.indexOf(input_source), + removeInputSource: (input_source) => { + if (input_source.name !== undefined) { + const name = input_source.name; + if (name >= 0 && name < 16) { + GodotWebXR.input_sources[name] = null; + } + + if (input_source.touch_index !== undefined) { + const touch_index = input_source.touch_index; + if (touch_index >= 0 && touch_index < 5) { + GodotWebXR.touches[touch_index] = null; + } + } + return name; + } + return -1; + }, + + getInputSourceId: (input_source) => { + if (input_source !== undefined) { + return input_source.name; + } + return -1; + }, + + getTouchIndex: (input_source) => { + if (input_source.touch_index !== undefined) { + return input_source.touch_index; + } + return -1; + }, }, godot_webxr_is_supported__proxy: 'sync', @@ -130,8 +231,8 @@ const GodotWebXR = { godot_webxr_initialize__deps: ['emscripten_webgl_get_current_context'], godot_webxr_initialize__proxy: 'sync', - godot_webxr_initialize__sig: 'viiiiiiiiii', - godot_webxr_initialize: function (p_session_mode, p_required_features, p_optional_features, p_requested_reference_spaces, p_on_session_started, p_on_session_ended, p_on_session_failed, p_on_controller_changed, p_on_input_event, p_on_simple_event) { + godot_webxr_initialize__sig: 'viiiiiiiii', + godot_webxr_initialize: function (p_session_mode, p_required_features, p_optional_features, p_requested_reference_spaces, p_on_session_started, p_on_session_ended, p_on_session_failed, p_on_input_event, p_on_simple_event) { GodotWebXR.monkeyPatchRequestAnimationFrame(true); const session_mode = GodotRuntime.parseString(p_session_mode); @@ -141,7 +242,6 @@ const GodotWebXR = { const onstarted = GodotRuntime.get_func(p_on_session_started); const onended = GodotRuntime.get_func(p_on_session_ended); const onfailed = GodotRuntime.get_func(p_on_session_failed); - const oncontroller = GodotRuntime.get_func(p_on_controller_changed); const oninputevent = GodotRuntime.get_func(p_on_input_event); const onsimpleevent = GodotRuntime.get_func(p_on_simple_event); @@ -161,24 +261,18 @@ const GodotWebXR = { }); session.addEventListener('inputsourceschange', function (evt) { - let controller_changed = false; - [evt.added, evt.removed].forEach((lst) => { - lst.forEach((input_source) => { - if (input_source.targetRayMode === 'tracked-pointer') { - controller_changed = true; - } - }); - }); - if (controller_changed) { - oncontroller(); - } + evt.added.forEach(GodotWebXR.addInputSource); + evt.removed.forEach(GodotWebXR.removeInputSource); }); - ['selectstart', 'select', 'selectend', 'squeezestart', 'squeeze', 'squeezeend'].forEach((input_event) => { + ['selectstart', 'selectend', 'squeezestart', 'squeezeend'].forEach((input_event, index) => { session.addEventListener(input_event, function (evt) { - const c_str = GodotRuntime.allocString(input_event); - oninputevent(c_str, GodotWebXR.getControllerId(evt.inputSource)); - GodotRuntime.free(c_str); + // Since this happens in-between normal frames, we need to + // grab the frame from the event in order to get poses for + // the input sources. + GodotWebXR.frame = evt.frame; + oninputevent(index, GodotWebXR.getInputSourceId(evt.inputSource)); + GodotWebXR.frame = null; }); }); @@ -193,9 +287,10 @@ const GodotWebXR = { GodotWebXR.gl = gl; gl.makeXRCompatible().then(function () { - session.updateRenderState({ - baseLayer: new XRWebGLLayer(session, gl), - }); + GodotWebXR.gl_binding = new XRWebGLBinding(session, gl); // eslint-disable-line no-undef + + // This will trigger the layer to get created. + GodotWebXR.getLayer(); function onReferenceSpaceSuccess(reference_space, reference_space_type) { GodotWebXR.space = reference_space; @@ -264,9 +359,14 @@ const GodotWebXR = { } GodotWebXR.session = null; + GodotWebXR.gl_binding = null; + GodotWebXR.layer = null; GodotWebXR.space = null; GodotWebXR.frame = null; GodotWebXR.pose = null; + GodotWebXR.view_count = 1; + GodotWebXR.input_sources = new Array(16); + GodotWebXR.touches = new Array(5); // Disable the monkey-patched window.requestAnimationFrame() and // pause/restart the main loop to activate it on all platforms. @@ -278,215 +378,186 @@ const GodotWebXR = { godot_webxr_get_view_count__sig: 'i', godot_webxr_get_view_count: function () { if (!GodotWebXR.session || !GodotWebXR.pose) { - return 0; + return 1; } - return GodotWebXR.pose.views.length; + const view_count = GodotWebXR.pose.views.length; + return view_count > 0 ? view_count : 1; }, godot_webxr_get_render_target_size__proxy: 'sync', - godot_webxr_get_render_target_size__sig: 'i', - godot_webxr_get_render_target_size: function () { - if (!GodotWebXR.session || !GodotWebXR.pose) { - return 0; + godot_webxr_get_render_target_size__sig: 'ii', + godot_webxr_get_render_target_size: function (r_size) { + const subimage = GodotWebXR.getSubImage(); + if (subimage === null) { + return false; } - const glLayer = GodotWebXR.session.renderState.baseLayer; - const view = GodotWebXR.pose.views[0]; - const viewport = glLayer.getViewport(view); + GodotRuntime.setHeapValue(r_size + 0, subimage.viewport.width, 'i32'); + GodotRuntime.setHeapValue(r_size + 4, subimage.viewport.height, 'i32'); - const buf = GodotRuntime.malloc(2 * 4); - GodotRuntime.setHeapValue(buf + 0, viewport.width, 'i32'); - GodotRuntime.setHeapValue(buf + 4, viewport.height, 'i32'); - return buf; + return true; }, - godot_webxr_get_transform_for_eye__proxy: 'sync', - godot_webxr_get_transform_for_eye__sig: 'ii', - godot_webxr_get_transform_for_eye: function (p_eye) { + godot_webxr_get_transform_for_view__proxy: 'sync', + godot_webxr_get_transform_for_view__sig: 'iii', + godot_webxr_get_transform_for_view: function (p_view, r_transform) { if (!GodotWebXR.session || !GodotWebXR.pose) { - return 0; + return false; } const views = GodotWebXR.pose.views; let matrix; - if (p_eye === 0) { - matrix = GodotWebXR.pose.transform.matrix; + if (p_view >= 0) { + matrix = views[p_view].transform.matrix; } else { - matrix = views[p_eye - 1].transform.matrix; - } - const buf = GodotRuntime.malloc(16 * 4); - for (let i = 0; i < 16; i++) { - GodotRuntime.setHeapValue(buf + (i * 4), matrix[i], 'float'); - } - return buf; - }, - - godot_webxr_get_projection_for_eye__proxy: 'sync', - godot_webxr_get_projection_for_eye__sig: 'ii', - godot_webxr_get_projection_for_eye: function (p_eye) { - if (!GodotWebXR.session || !GodotWebXR.pose) { - return 0; + // For -1 (or any other negative value) return the HMD transform. + matrix = GodotWebXR.pose.transform.matrix; } - const view_index = (p_eye === 2 /* ARVRInterface::EYE_RIGHT */) ? 1 : 0; - const matrix = GodotWebXR.pose.views[view_index].projectionMatrix; - const buf = GodotRuntime.malloc(16 * 4); for (let i = 0; i < 16; i++) { - GodotRuntime.setHeapValue(buf + (i * 4), matrix[i], 'float'); + GodotRuntime.setHeapValue(r_transform + (i * 4), matrix[i], 'float'); } - return buf; + + return true; }, - godot_webxr_commit__proxy: 'sync', - godot_webxr_commit__sig: 'vi', - godot_webxr_commit: function (p_texture) { + godot_webxr_get_projection_for_view__proxy: 'sync', + godot_webxr_get_projection_for_view__sig: 'iii', + godot_webxr_get_projection_for_view: function (p_view, r_transform) { if (!GodotWebXR.session || !GodotWebXR.pose) { - return; + return false; } - const glLayer = GodotWebXR.session.renderState.baseLayer; - const views = GodotWebXR.pose.views; - const gl = GodotWebXR.gl; - - const texture = GL.textures[p_texture]; - - const orig_framebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING); - const orig_read_framebuffer = gl.getParameter(gl.READ_FRAMEBUFFER_BINDING); - const orig_read_buffer = gl.getParameter(gl.READ_BUFFER); - const orig_draw_framebuffer = gl.getParameter(gl.DRAW_FRAMEBUFFER_BINDING); - - // Copy from Godot render target into framebuffer from WebXR. - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - for (let i = 0; i < views.length; i++) { - const viewport = glLayer.getViewport(views[i]); - - const read_fbo = gl.createFramebuffer(); - gl.bindFramebuffer(gl.READ_FRAMEBUFFER, read_fbo); - if (views.length > 1) { - gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture, 0, i); - } else { - gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); - } - gl.readBuffer(gl.COLOR_ATTACHMENT0); - gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, glLayer.framebuffer); - - // Flip Y upside down on destination. - gl.blitFramebuffer(0, 0, viewport.width, viewport.height, - viewport.x, viewport.y + viewport.height, viewport.x + viewport.width, viewport.y, - gl.COLOR_BUFFER_BIT, gl.NEAREST); - - gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null); - gl.deleteFramebuffer(read_fbo); + const matrix = GodotWebXR.pose.views[p_view].projectionMatrix; + for (let i = 0; i < 16; i++) { + GodotRuntime.setHeapValue(r_transform + (i * 4), matrix[i], 'float'); } - // Restore state. - gl.bindFramebuffer(gl.FRAMEBUFFER, orig_framebuffer); - gl.bindFramebuffer(gl.READ_FRAMEBUFFER, orig_read_framebuffer); - gl.readBuffer(orig_read_buffer); - gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, orig_draw_framebuffer); + return true; }, - godot_webxr_sample_controller_data__proxy: 'sync', - godot_webxr_sample_controller_data__sig: 'v', - godot_webxr_sample_controller_data: function () { - GodotWebXR.sampleControllers(); + godot_webxr_get_color_texture__proxy: 'sync', + godot_webxr_get_color_texture__sig: 'i', + godot_webxr_get_color_texture: function () { + const subimage = GodotWebXR.getSubImage(); + if (subimage === null) { + return 0; + } + return GodotWebXR.getTextureId(subimage.colorTexture); }, - godot_webxr_get_controller_count__proxy: 'sync', - godot_webxr_get_controller_count__sig: 'i', - godot_webxr_get_controller_count: function () { - if (!GodotWebXR.session || !GodotWebXR.frame) { + godot_webxr_get_depth_texture__proxy: 'sync', + godot_webxr_get_depth_texture__sig: 'i', + godot_webxr_get_depth_texture: function () { + const subimage = GodotWebXR.getSubImage(); + if (subimage === null) { return 0; } - return GodotWebXR.controllers.length; + if (!subimage.depthStencilTexture) { + return 0; + } + return GodotWebXR.getTextureId(subimage.depthStencilTexture); }, - godot_webxr_is_controller_connected__proxy: 'sync', - godot_webxr_is_controller_connected__sig: 'ii', - godot_webxr_is_controller_connected: function (p_controller) { - if (!GodotWebXR.session || !GodotWebXR.frame) { - return false; + godot_webxr_get_velocity_texture__proxy: 'sync', + godot_webxr_get_velocity_texture__sig: 'i', + godot_webxr_get_velocity_texture: function () { + const subimage = GodotWebXR.getSubImage(); + if (subimage === null) { + return 0; + } + if (!subimage.motionVectorTexture) { + return 0; } - return !!GodotWebXR.controllers[p_controller]; + return GodotWebXR.getTextureId(subimage.motionVectorTexture); }, - godot_webxr_get_controller_transform__proxy: 'sync', - godot_webxr_get_controller_transform__sig: 'ii', - godot_webxr_get_controller_transform: function (p_controller) { + godot_webxr_update_input_source__proxy: 'sync', + godot_webxr_update_input_source__sig: 'iiiiiiiiiiii', + godot_webxr_update_input_source: function (p_input_source_id, r_target_pose, r_target_ray_mode, r_touch_index, r_has_grip_pose, r_grip_pose, r_has_standard_mapping, r_button_count, r_buttons, r_axes_count, r_axes) { if (!GodotWebXR.session || !GodotWebXR.frame) { return 0; } - const controller = GodotWebXR.controllers[p_controller]; - if (!controller) { - return 0; + if (p_input_source_id < 0 || p_input_source_id >= GodotWebXR.input_sources.length || !GodotWebXR.input_sources[p_input_source_id]) { + return false; } + const input_source = GodotWebXR.input_sources[p_input_source_id]; const frame = GodotWebXR.frame; const space = GodotWebXR.space; - const pose = frame.getPose(controller.targetRaySpace, space); - if (!pose) { + // Target pose. + const target_pose = frame.getPose(input_source.targetRaySpace, space); + if (!target_pose) { // This can mean that the controller lost tracking. - return 0; + return false; } - const matrix = pose.transform.matrix; - - const buf = GodotRuntime.malloc(16 * 4); + const target_pose_matrix = target_pose.transform.matrix; for (let i = 0; i < 16; i++) { - GodotRuntime.setHeapValue(buf + (i * 4), matrix[i], 'float'); + GodotRuntime.setHeapValue(r_target_pose + (i * 4), target_pose_matrix[i], 'float'); } - return buf; - }, - godot_webxr_get_controller_buttons__proxy: 'sync', - godot_webxr_get_controller_buttons__sig: 'ii', - godot_webxr_get_controller_buttons: function (p_controller) { - if (GodotWebXR.controllers.length === 0) { - return 0; - } + // Target ray mode. + let target_ray_mode = 0; + switch (input_source.targetRayMode) { + case 'gaze': + target_ray_mode = 1; + break; - const controller = GodotWebXR.controllers[p_controller]; - if (!controller || !controller.gamepad) { - return 0; - } - - const button_count = controller.gamepad.buttons.length; + case 'tracked-pointer': + target_ray_mode = 2; + break; - const buf = GodotRuntime.malloc((button_count + 1) * 4); - GodotRuntime.setHeapValue(buf, button_count, 'i32'); - for (let i = 0; i < button_count; i++) { - GodotRuntime.setHeapValue(buf + 4 + (i * 4), controller.gamepad.buttons[i].value, 'float'); - } - return buf; - }, + case 'screen': + target_ray_mode = 3; + break; - godot_webxr_get_controller_axes__proxy: 'sync', - godot_webxr_get_controller_axes__sig: 'ii', - godot_webxr_get_controller_axes: function (p_controller) { - if (GodotWebXR.controllers.length === 0) { - return 0; + default: } - - const controller = GodotWebXR.controllers[p_controller]; - if (!controller || !controller.gamepad) { - return 0; + GodotRuntime.setHeapValue(r_target_ray_mode, target_ray_mode, 'i32'); + + // Touch index. + GodotRuntime.setHeapValue(r_touch_index, GodotWebXR.getTouchIndex(input_source), 'i32'); + + // Grip pose. + let has_grip_pose = false; + if (input_source.gripSpace) { + const grip_pose = frame.getPose(input_source.gripSpace, space); + if (grip_pose) { + const grip_pose_matrix = grip_pose.transform.matrix; + for (let i = 0; i < 16; i++) { + GodotRuntime.setHeapValue(r_grip_pose + (i * 4), grip_pose_matrix[i], 'float'); + } + has_grip_pose = true; + } } + GodotRuntime.setHeapValue(r_has_grip_pose, has_grip_pose ? 1 : 0, 'i32'); + + // Gamepad data (mapping, buttons and axes). + let has_standard_mapping = false; + let button_count = 0; + let axes_count = 0; + if (input_source.gamepad) { + if (input_source.gamepad.mapping === 'xr-standard') { + has_standard_mapping = true; + } - const axes_count = controller.gamepad.axes.length; + button_count = Math.min(input_source.gamepad.buttons.length, 10); + for (let i = 0; i < button_count; i++) { + GodotRuntime.setHeapValue(r_buttons + (i * 4), input_source.gamepad.buttons[i].value, 'float'); + } - const buf = GodotRuntime.malloc((axes_count + 1) * 4); - GodotRuntime.setHeapValue(buf, axes_count, 'i32'); - for (let i = 0; i < axes_count; i++) { - let value = controller.gamepad.axes[i]; - if (i === 1 || i === 3) { - // Invert the Y-axis on thumbsticks and trackpads, in order to - // match OpenXR and other XR platform SDKs. - value *= -1.0; + axes_count = Math.min(input_source.gamepad.axes.length, 10); + for (let i = 0; i < axes_count; i++) { + GodotRuntime.setHeapValue(r_axes + (i * 4), input_source.gamepad.axes[i], 'float'); } - GodotRuntime.setHeapValue(buf + 4 + (i * 4), value, 'float'); } - return buf; + GodotRuntime.setHeapValue(r_has_standard_mapping, has_standard_mapping ? 1 : 0, 'i32'); + GodotRuntime.setHeapValue(r_button_count, button_count, 'i32'); + GodotRuntime.setHeapValue(r_axes_count, axes_count, 'i32'); + + return true; }, godot_webxr_get_visibility_state__proxy: 'sync', @@ -500,8 +571,8 @@ const GodotWebXR = { }, godot_webxr_get_bounds_geometry__proxy: 'sync', - godot_webxr_get_bounds_geometry__sig: 'i', - godot_webxr_get_bounds_geometry: function () { + godot_webxr_get_bounds_geometry__sig: 'ii', + godot_webxr_get_bounds_geometry: function (r_points) { if (!GodotWebXR.space || !GodotWebXR.space.boundsGeometry) { return 0; } @@ -511,7 +582,7 @@ const GodotWebXR = { return 0; } - const buf = GodotRuntime.malloc(((point_count * 3) + 1) * 4); + const buf = GodotRuntime.malloc(point_count * 3 * 4); GodotRuntime.setHeapValue(buf, point_count, 'i32'); for (let i = 0; i < point_count; i++) { const point = GodotWebXR.space.boundsGeometry[i]; @@ -519,8 +590,9 @@ const GodotWebXR = { GodotRuntime.setHeapValue(buf + ((i * 3) + 2) * 4, point.y, 'float'); GodotRuntime.setHeapValue(buf + ((i * 3) + 3) * 4, point.z, 'float'); } + GodotRuntime.setHeapValue(r_points, buf, 'i32'); - return buf; + return point_count; }, }; diff --git a/modules/webxr/native/webxr.externs.js b/modules/webxr/native/webxr.externs.js index 9ea105aa93..4b88820b19 100644 --- a/modules/webxr/native/webxr.externs.js +++ b/modules/webxr/native/webxr.externs.js @@ -1,3 +1,7 @@ +/* + * WebXR Device API + */ + /** * @type {XR} */ @@ -497,3 +501,681 @@ XRPose.prototype.transform; * @type {boolean} */ XRPose.prototype.emulatedPosition; + +/* + * WebXR Layers API Level 1 + */ + +/** + * @constructor XRLayer + */ +function XRLayer() {} + +/** + * @constructor XRLayerEventInit + */ +function XRLayerEventInit() {} + +/** + * @type {XRLayer} + */ +XRLayerEventInit.prototype.layer; + +/** + * @constructor XRLayerEvent + * + * @param {string} type + * @param {XRLayerEventInit} init + */ +function XRLayerEvent(type, init) {}; + +/** + * @type {XRLayer} + */ +XRLayerEvent.prototype.layer; + +/** + * @constructor XRCompositionLayer + * @extends {XRLayer} + */ +function XRCompositionLayer() {}; + +/** + * @type {string} + */ +XRCompositionLayer.prototype.layout; + +/** + * @type {boolean} + */ +XRCompositionLayer.prototype.blendTextureAberrationCorrection; + +/** + * @type {?boolean} + */ +XRCompositionLayer.prototype.chromaticAberrationCorrection; + +/** + * @type {boolean} + */ +XRCompositionLayer.prototype.forceMonoPresentation; + +/** + * @type {number} + */ +XRCompositionLayer.prototype.opacity; + +/** + * @type {number} + */ +XRCompositionLayer.prototype.mipLevels; + +/** + * @type {boolean} + */ +XRCompositionLayer.prototype.needsRedraw; + +/** + * @return {void} + */ +XRCompositionLayer.prototype.destroy = function () {}; + +/** + * @constructor XRProjectionLayer + * @extends {XRCompositionLayer} + */ +function XRProjectionLayer() {} + +/** + * @type {number} + */ +XRProjectionLayer.prototype.textureWidth; + +/** + * @type {number} + */ +XRProjectionLayer.prototype.textureHeight; + +/** + * @type {number} + */ +XRProjectionLayer.prototype.textureArrayLength; + +/** + * @type {boolean} + */ +XRProjectionLayer.prototype.ignoreDepthValues; + +/** + * @type {?number} + */ +XRProjectionLayer.prototype.fixedFoveation; + +/** + * @type {XRRigidTransform} + */ +XRProjectionLayer.prototype.deltaPose; + +/** + * @constructor XRQuadLayer + * @extends {XRCompositionLayer} + */ +function XRQuadLayer() {} + +/** + * @type {XRSpace} + */ +XRQuadLayer.prototype.space; + +/** + * @type {XRRigidTransform} + */ +XRQuadLayer.prototype.transform; + +/** + * @type {number} + */ +XRQuadLayer.prototype.width; + +/** + * @type {number} + */ +XRQuadLayer.prototype.height; + +/** + * @type {?function (XRLayerEvent)} + */ +XRQuadLayer.prototype.onredraw; + +/** + * @constructor XRCylinderLayer + * @extends {XRCompositionLayer} + */ +function XRCylinderLayer() {} + +/** + * @type {XRSpace} + */ +XRCylinderLayer.prototype.space; + +/** + * @type {XRRigidTransform} + */ +XRCylinderLayer.prototype.transform; + +/** + * @type {number} + */ +XRCylinderLayer.prototype.radius; + +/** + * @type {number} + */ +XRCylinderLayer.prototype.centralAngle; + +/** + * @type {number} + */ +XRCylinderLayer.prototype.aspectRatio; + +/** + * @type {?function (XRLayerEvent)} + */ +XRCylinderLayer.prototype.onredraw; + +/** + * @constructor XREquirectLayer + * @extends {XRCompositionLayer} + */ +function XREquirectLayer() {} + +/** + * @type {XRSpace} + */ +XREquirectLayer.prototype.space; + +/** + * @type {XRRigidTransform} + */ +XREquirectLayer.prototype.transform; + +/** + * @type {number} + */ +XREquirectLayer.prototype.radius; + +/** + * @type {number} + */ +XREquirectLayer.prototype.centralHorizontalAngle; + +/** + * @type {number} + */ +XREquirectLayer.prototype.upperVerticalAngle; + +/** + * @type {number} + */ +XREquirectLayer.prototype.lowerVerticalAngle; + +/** + * @type {?function (XRLayerEvent)} + */ +XREquirectLayer.prototype.onredraw; + +/** + * @constructor XRCubeLayer + * @extends {XRCompositionLayer} + */ +function XRCubeLayer() {} + +/** + * @type {XRSpace} + */ +XRCubeLayer.prototype.space; + +/** + * @type {DOMPointReadOnly} + */ +XRCubeLayer.prototype.orientation; + +/** + * @type {?function (XRLayerEvent)} + */ +XRCubeLayer.prototype.onredraw; + +/** + * @constructor XRSubImage + */ +function XRSubImage() {} + +/** + * @type {XRViewport} + */ +XRSubImage.prototype.viewport; + +/** + * @constructor XRWebGLSubImage + * @extends {XRSubImage} + */ +function XRWebGLSubImage () {} + +/** + * @type {WebGLTexture} + */ +XRWebGLSubImage.prototype.colorTexture; + +/** + * @type {?WebGLTexture} + */ +XRWebGLSubImage.prototype.depthStencilTexture; + +/** + * @type {?WebGLTexture} + */ +XRWebGLSubImage.prototype.motionVectorTexture; + +/** + * @type {?number} + */ +XRWebGLSubImage.prototype.imageIndex; + +/** + * @type {number} + */ +XRWebGLSubImage.prototype.colorTextureWidth; + +/** + * @type {number} + */ +XRWebGLSubImage.prototype.colorTextureHeight; + +/** + * @type {?number} + */ +XRWebGLSubImage.prototype.depthStencilTextureWidth; + +/** + * @type {?number} + */ +XRWebGLSubImage.prototype.depthStencilTextureHeight; + +/** + * @type {?number} + */ + +XRWebGLSubImage.prototype.motionVectorTextureWidth; + +/** + * @type {?number} + */ +XRWebGLSubImage.prototype.motionVectorTextureHeight; + +/** + * @constructor XRProjectionLayerInit + */ +function XRProjectionLayerInit() {} + +/** + * @type {string} + */ +XRProjectionLayerInit.prototype.textureType; + +/** + * @type {number} + */ +XRProjectionLayerInit.prototype.colorFormat; + +/** + * @type {number} + */ +XRProjectionLayerInit.prototype.depthFormat; + +/** + * @type {number} + */ +XRProjectionLayerInit.prototype.scaleFactor; + +/** + * @constructor XRLayerInit + */ +function XRLayerInit() {} + +/** + * @type {XRSpace} + */ +XRLayerInit.prototype.space; + +/** + * @type {number} + */ +XRLayerInit.prototype.colorFormat; + +/** + * @type {number} + */ +XRLayerInit.prototype.depthFormat; + +/** + * @type {number} + */ +XRLayerInit.prototype.mipLevels; + +/** + * @type {number} + */ +XRLayerInit.prototype.viewPixelWidth; + +/** + * @type {number} + */ +XRLayerInit.prototype.viewPixelHeight; + +/** + * @type {string} + */ +XRLayerInit.prototype.layout; + +/** + * @type {boolean} + */ +XRLayerInit.prototype.isStatic; + +/** + * @constructor XRQuadLayerInit + * @extends {XRLayerInit} + */ +function XRQuadLayerInit() {} + +/** + * @type {string} + */ +XRQuadLayerInit.prototype.textureType; + +/** + * @type {?XRRigidTransform} + */ +XRQuadLayerInit.prototype.transform; + +/** + * @type {number} + */ +XRQuadLayerInit.prototype.width; + +/** + * @type {number} + */ +XRQuadLayerInit.prototype.height; + +/** + * @constructor XRCylinderLayerInit + * @extends {XRLayerInit} + */ +function XRCylinderLayerInit() {} + +/** + * @type {string} + */ +XRCylinderLayerInit.prototype.textureType; + +/** + * @type {?XRRigidTransform} + */ +XRCylinderLayerInit.prototype.transform; + +/** + * @type {number} + */ +XRCylinderLayerInit.prototype.radius; + +/** + * @type {number} + */ +XRCylinderLayerInit.prototype.centralAngle; + +/** + * @type {number} + */ +XRCylinderLayerInit.prototype.aspectRatio; + +/** + * @constructor XREquirectLayerInit + * @extends {XRLayerInit} + */ +function XREquirectLayerInit() {} + +/** + * @type {string} + */ +XREquirectLayerInit.prototype.textureType; + +/** + * @type {?XRRigidTransform} + */ +XREquirectLayerInit.prototype.transform; + +/** + * @type {number} + */ +XREquirectLayerInit.prototype.radius; + +/** + * @type {number} + */ +XREquirectLayerInit.prototype.centralHorizontalAngle; + +/** + * @type {number} + */ +XREquirectLayerInit.prototype.upperVerticalAngle; + +/** + * @type {number} + */ +XREquirectLayerInit.prototype.lowerVerticalAngle; + +/** + * @constructor XRCubeLayerInit + * @extends {XRLayerInit} + */ +function XRCubeLayerInit() {} + +/** + * @type {DOMPointReadOnly} + */ +XRCubeLayerInit.prototype.orientation; + +/** + * @constructor XRWebGLBinding + * + * @param {XRSession} session + * @param {WebGLRenderContext|WebGL2RenderingContext} context + */ +function XRWebGLBinding(session, context) {} + +/** + * @type {number} + */ +XRWebGLBinding.prototype.nativeProjectionScaleFactor; + +/** + * @type {number} + */ +XRWebGLBinding.prototype.usesDepthValues; + +/** + * @param {XRProjectionLayerInit} init + * @return {XRProjectionLayer} + */ +XRWebGLBinding.prototype.createProjectionLayer = function (init) {}; + +/** + * @param {XRQuadLayerInit} init + * @return {XRQuadLayer} + */ +XRWebGLBinding.prototype.createQuadLayer = function (init) {}; + +/** + * @param {XRCylinderLayerInit} init + * @return {XRCylinderLayer} + */ +XRWebGLBinding.prototype.createCylinderLayer = function (init) {}; + +/** + * @param {XREquirectLayerInit} init + * @return {XREquirectLayer} + */ +XRWebGLBinding.prototype.createEquirectLayer = function (init) {}; + +/** + * @param {XRCubeLayerInit} init + * @return {XRCubeLayer} + */ +XRWebGLBinding.prototype.createCubeLayer = function (init) {}; + +/** + * @param {XRCompositionLayer} layer + * @param {XRFrame} frame + * @param {string} eye + * @return {XRWebGLSubImage} + */ +XRWebGLBinding.prototype.getSubImage = function (layer, frame, eye) {}; + +/** + * @param {XRProjectionLayer} layer + * @param {XRView} view + * @return {XRWebGLSubImage} + */ +XRWebGLBinding.prototype.getViewSubImage = function (layer, view) {}; + +/** + * @constructor XRMediaLayerInit + */ +function XRMediaLayerInit() {} + +/** + * @type {XRSpace} + */ +XRMediaLayerInit.prototype.space; + +/** + * @type {string} + */ +XRMediaLayerInit.prototype.layout; + +/** + * @type {boolean} + */ +XRMediaLayerInit.prototype.invertStereo; + +/** + * @constructor XRMediaQuadLayerInit + * @extends {XRMediaLayerInit} + */ +function XRMediaQuadLayerInit() {} + +/** + * @type {XRRigidTransform} + */ +XRMediaQuadLayerInit.prototype.transform; + +/** + * @type {number} + */ +XRMediaQuadLayerInit.prototype.width; + +/** + * @type {number} + */ +XRMediaQuadLayerInit.prototype.height; + +/** + * @constructor XRMediaCylinderLayerInit + * @extends {XRMediaLayerInit} + */ +function XRMediaCylinderLayerInit() {} + +/** + * @type {XRRigidTransform} + */ +XRMediaCylinderLayerInit.prototype.transform; + +/** + * @type {number} + */ +XRMediaCylinderLayerInit.prototype.radius; + +/** + * @type {number} + */ +XRMediaCylinderLayerInit.prototype.centralAngle; + +/** + * @type {?number} + */ +XRMediaCylinderLayerInit.prototype.aspectRatio; + +/** + * @constructor XRMediaEquirectLayerInit + * @extends {XRMediaLayerInit} + */ +function XRMediaEquirectLayerInit() {} + +/** + * @type {XRRigidTransform} + */ +XRMediaEquirectLayerInit.prototype.transform; + +/** + * @type {number} + */ +XRMediaEquirectLayerInit.prototype.radius; + +/** + * @type {number} + */ +XRMediaEquirectLayerInit.prototype.centralHorizontalAngle; + +/** + * @type {number} + */ +XRMediaEquirectLayerInit.prototype.upperVerticalAngle; + +/** + * @type {number} + */ +XRMediaEquirectLayerInit.prototype.lowerVerticalAngle; + +/** + * @constructor XRMediaBinding + * + * @param {XRSession} session + */ +function XRMediaBinding(session) {} + +/** + * @param {HTMLVideoElement} video + * @param {XRMediaQuadLayerInit} init + * @return {XRQuadLayer} + */ +XRMediaBinding.prototype.createQuadLayer = function(video, init) {}; + +/** + * @param {HTMLVideoElement} video + * @param {XRMediaCylinderLayerInit} init + * @return {XRCylinderLayer} + */ +XRMediaBinding.prototype.createCylinderLayer = function(video, init) {}; + +/** + * @param {HTMLVideoElement} video + * @param {XRMediaEquirectLayerInit} init + * @return {XREquirectLayer} + */ +XRMediaBinding.prototype.createEquirectLayer = function(video, init) {}; + +/** + * @type {Array<XRLayer>} + */ +XRRenderState.prototype.layers; diff --git a/modules/webxr/webxr_interface.cpp b/modules/webxr/webxr_interface.cpp index b0ad53523a..c0580df172 100644 --- a/modules/webxr/webxr_interface.cpp +++ b/modules/webxr/webxr_interface.cpp @@ -42,9 +42,10 @@ void WebXRInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("get_reference_space_type"), &WebXRInterface::get_reference_space_type); ClassDB::bind_method(D_METHOD("set_requested_reference_space_types", "requested_reference_space_types"), &WebXRInterface::set_requested_reference_space_types); ClassDB::bind_method(D_METHOD("get_requested_reference_space_types"), &WebXRInterface::get_requested_reference_space_types); - ClassDB::bind_method(D_METHOD("get_controller", "controller_id"), &WebXRInterface::get_controller); + ClassDB::bind_method(D_METHOD("is_input_source_active", "input_source_id"), &WebXRInterface::is_input_source_active); + ClassDB::bind_method(D_METHOD("get_input_source_tracker", "input_source_id"), &WebXRInterface::get_input_source_tracker); + ClassDB::bind_method(D_METHOD("get_input_source_target_ray_mode", "input_source_id"), &WebXRInterface::get_input_source_target_ray_mode); ClassDB::bind_method(D_METHOD("get_visibility_state"), &WebXRInterface::get_visibility_state); - ClassDB::bind_method(D_METHOD("get_bounds_geometry"), &WebXRInterface::get_bounds_geometry); ADD_PROPERTY(PropertyInfo(Variant::STRING, "session_mode", PROPERTY_HINT_NONE), "set_session_mode", "get_session_mode"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "required_features", PROPERTY_HINT_NONE), "set_required_features", "get_required_features"); @@ -52,20 +53,24 @@ void WebXRInterface::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "requested_reference_space_types", PROPERTY_HINT_NONE), "set_requested_reference_space_types", "get_requested_reference_space_types"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "reference_space_type", PROPERTY_HINT_NONE), "", "get_reference_space_type"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "visibility_state", PROPERTY_HINT_NONE), "", "get_visibility_state"); - ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "bounds_geometry", PROPERTY_HINT_NONE), "", "get_bounds_geometry"); ADD_SIGNAL(MethodInfo("session_supported", PropertyInfo(Variant::STRING, "session_mode"), PropertyInfo(Variant::BOOL, "supported"))); ADD_SIGNAL(MethodInfo("session_started")); ADD_SIGNAL(MethodInfo("session_ended")); ADD_SIGNAL(MethodInfo("session_failed", PropertyInfo(Variant::STRING, "message"))); - ADD_SIGNAL(MethodInfo("selectstart", PropertyInfo(Variant::INT, "controller_id"))); - ADD_SIGNAL(MethodInfo("select", PropertyInfo(Variant::INT, "controller_id"))); - ADD_SIGNAL(MethodInfo("selectend", PropertyInfo(Variant::INT, "controller_id"))); - ADD_SIGNAL(MethodInfo("squeezestart", PropertyInfo(Variant::INT, "controller_id"))); - ADD_SIGNAL(MethodInfo("squeeze", PropertyInfo(Variant::INT, "controller_id"))); - ADD_SIGNAL(MethodInfo("squeezeend", PropertyInfo(Variant::INT, "controller_id"))); + ADD_SIGNAL(MethodInfo("selectstart", PropertyInfo(Variant::INT, "input_source_id"))); + ADD_SIGNAL(MethodInfo("select", PropertyInfo(Variant::INT, "input_source_id"))); + ADD_SIGNAL(MethodInfo("selectend", PropertyInfo(Variant::INT, "input_source_id"))); + ADD_SIGNAL(MethodInfo("squeezestart", PropertyInfo(Variant::INT, "input_source_id"))); + ADD_SIGNAL(MethodInfo("squeeze", PropertyInfo(Variant::INT, "input_source_id"))); + ADD_SIGNAL(MethodInfo("squeezeend", PropertyInfo(Variant::INT, "input_source_id"))); ADD_SIGNAL(MethodInfo("visibility_state_changed")); ADD_SIGNAL(MethodInfo("reference_space_reset")); + + BIND_ENUM_CONSTANT(TARGET_RAY_MODE_UNKNOWN); + BIND_ENUM_CONSTANT(TARGET_RAY_MODE_GAZE); + BIND_ENUM_CONSTANT(TARGET_RAY_MODE_TRACKED_POINTER); + BIND_ENUM_CONSTANT(TARGET_RAY_MODE_SCREEN); } diff --git a/modules/webxr/webxr_interface.h b/modules/webxr/webxr_interface.h index 801643bfa6..1afeb5bab0 100644 --- a/modules/webxr/webxr_interface.h +++ b/modules/webxr/webxr_interface.h @@ -45,6 +45,13 @@ protected: static void _bind_methods(); public: + enum TargetRayMode { + TARGET_RAY_MODE_UNKNOWN, + TARGET_RAY_MODE_GAZE, + TARGET_RAY_MODE_TRACKED_POINTER, + TARGET_RAY_MODE_SCREEN, + }; + virtual void is_session_supported(const String &p_session_mode) = 0; virtual void set_session_mode(String p_session_mode) = 0; virtual String get_session_mode() const = 0; @@ -55,9 +62,12 @@ public: virtual void set_requested_reference_space_types(String p_requested_reference_space_types) = 0; virtual String get_requested_reference_space_types() const = 0; virtual String get_reference_space_type() const = 0; - virtual Ref<XRPositionalTracker> get_controller(int p_controller_id) const = 0; + virtual bool is_input_source_active(int p_input_source_id) const = 0; + virtual Ref<XRPositionalTracker> get_input_source_tracker(int p_input_source_id) const = 0; + virtual TargetRayMode get_input_source_target_ray_mode(int p_input_source_id) const = 0; virtual String get_visibility_state() const = 0; - virtual PackedVector3Array get_bounds_geometry() const = 0; }; +VARIANT_ENUM_CAST(WebXRInterface::TargetRayMode); + #endif // WEBXR_INTERFACE_H diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp index f6ed9f027e..265f6626a7 100644 --- a/modules/webxr/webxr_interface_js.cpp +++ b/modules/webxr/webxr_interface_js.cpp @@ -37,6 +37,8 @@ #include "drivers/gles3/storage/texture_storage.h" #include "emscripten.h" #include "godot_webxr.h" +#include "scene/main/scene_tree.h" +#include "scene/main/window.h" #include "servers/rendering/renderer_compositor.h" #include "servers/rendering/rendering_server_globals.h" @@ -89,25 +91,14 @@ void _emwebxr_on_session_failed(char *p_message) { interface->emit_signal(SNAME("session_failed"), message); } -void _emwebxr_on_controller_changed() { +extern "C" EMSCRIPTEN_KEEPALIVE void _emwebxr_on_input_event(int p_event_type, int p_input_source_id) { XRServer *xr_server = XRServer::get_singleton(); ERR_FAIL_NULL(xr_server); Ref<XRInterface> interface = xr_server->find_interface("WebXR"); ERR_FAIL_COND(interface.is_null()); - static_cast<WebXRInterfaceJS *>(interface.ptr())->_on_controller_changed(); -} - -extern "C" EMSCRIPTEN_KEEPALIVE void _emwebxr_on_input_event(char *p_signal_name, int p_input_source) { - XRServer *xr_server = XRServer::get_singleton(); - ERR_FAIL_NULL(xr_server); - - Ref<XRInterface> interface = xr_server->find_interface("WebXR"); - ERR_FAIL_COND(interface.is_null()); - - StringName signal_name = StringName(p_signal_name); - interface->emit_signal(signal_name, p_input_source + 1); + ((WebXRInterfaceJS *)interface.ptr())->_on_input_event(p_event_type, p_input_source_id); } extern "C" EMSCRIPTEN_KEEPALIVE void _emwebxr_on_simple_event(char *p_signal_name) { @@ -165,16 +156,22 @@ String WebXRInterfaceJS::get_reference_space_type() const { return reference_space_type; } -Ref<XRPositionalTracker> WebXRInterfaceJS::get_controller(int p_controller_id) const { - XRServer *xr_server = XRServer::get_singleton(); - ERR_FAIL_NULL_V(xr_server, Ref<XRPositionalTracker>()); +bool WebXRInterfaceJS::is_input_source_active(int p_input_source_id) const { + ERR_FAIL_INDEX_V(p_input_source_id, input_source_count, false); + return input_sources[p_input_source_id].active; +} - // TODO support more then two controllers - if (p_controller_id >= 0 && p_controller_id < 2) { - return controllers[p_controller_id]; - }; +Ref<XRPositionalTracker> WebXRInterfaceJS::get_input_source_tracker(int p_input_source_id) const { + ERR_FAIL_INDEX_V(p_input_source_id, input_source_count, Ref<XRPositionalTracker>()); + return input_sources[p_input_source_id].tracker; +} - return Ref<XRPositionalTracker>(); +WebXRInterface::TargetRayMode WebXRInterfaceJS::get_input_source_target_ray_mode(int p_input_source_id) const { + ERR_FAIL_INDEX_V(p_input_source_id, input_source_count, WebXRInterface::TARGET_RAY_MODE_UNKNOWN); + if (!input_sources[p_input_source_id].active) { + return WebXRInterface::TARGET_RAY_MODE_UNKNOWN; + } + return input_sources[p_input_source_id].target_ray_mode; } String WebXRInterfaceJS::get_visibility_state() const { @@ -188,17 +185,18 @@ String WebXRInterfaceJS::get_visibility_state() const { return String(); } -PackedVector3Array WebXRInterfaceJS::get_bounds_geometry() const { +PackedVector3Array WebXRInterfaceJS::get_play_area() const { PackedVector3Array ret; - int *js_bounds = godot_webxr_get_bounds_geometry(); - if (js_bounds) { - ret.resize(js_bounds[0]); - for (int i = 0; i < js_bounds[0]; i++) { - float *js_vector3 = ((float *)js_bounds) + (i * 3) + 1; + float *points; + int point_count = godot_webxr_get_bounds_geometry(&points); + if (point_count > 0) { + ret.resize(point_count); + for (int i = 0; i < point_count; i++) { + float *js_vector3 = points + (i * 3); ret.set(i, Vector3(js_vector3[0], js_vector3[1], js_vector3[2])); } - free(js_bounds); + free(points); } return ret; @@ -209,7 +207,7 @@ StringName WebXRInterfaceJS::get_name() const { }; uint32_t WebXRInterfaceJS::get_capabilities() const { - return XRInterface::XR_STEREO | XRInterface::XR_MONO; + return XRInterface::XR_STEREO | XRInterface::XR_MONO | XRInterface::XR_VR | XRInterface::XR_AR; }; uint32_t WebXRInterfaceJS::get_view_count() { @@ -261,7 +259,6 @@ bool WebXRInterfaceJS::initialize() { &_emwebxr_on_session_started, &_emwebxr_on_session_ended, &_emwebxr_on_session_failed, - &_emwebxr_on_controller_changed, &_emwebxr_on_input_event, &_emwebxr_on_simple_event); }; @@ -287,6 +284,18 @@ void WebXRInterfaceJS::uninitialize() { godot_webxr_uninitialize(); + GLES3::TextureStorage *texture_storage = dynamic_cast<GLES3::TextureStorage *>(RSG::texture_storage); + if (texture_storage != nullptr) { + for (KeyValue<unsigned int, RID> &E : texture_cache) { + // Forcibly mark as not part of a render target so we can free it. + GLES3::Texture *texture = texture_storage->get_texture(E.value); + texture->is_render_target = false; + + texture_storage->texture_free(E.value); + } + } + + texture_cache.clear(); reference_space_type = ""; initialized = false; }; @@ -316,27 +325,26 @@ Size2 WebXRInterfaceJS::get_render_target_size() { return render_targetsize; } - int *js_size = godot_webxr_get_render_target_size(); - if (!initialized || js_size == nullptr) { - // As a temporary default (until WebXR is fully initialized), use half the window size. - Size2 temp = DisplayServer::get_singleton()->window_get_size(); - temp.width /= 2.0; - return temp; - } + int js_size[2]; + bool has_size = godot_webxr_get_render_target_size(js_size); - render_targetsize.width = js_size[0]; - render_targetsize.height = js_size[1]; + if (!initialized || !has_size) { + // As a temporary default (until WebXR is fully initialized), use the + // window size. + return DisplayServer::get_singleton()->window_get_size(); + } - free(js_size); + render_targetsize.width = (float)js_size[0]; + render_targetsize.height = (float)js_size[1]; return render_targetsize; }; Transform3D WebXRInterfaceJS::get_camera_transform() { - Transform3D transform_for_eye; + Transform3D camera_transform; XRServer *xr_server = XRServer::get_singleton(); - ERR_FAIL_NULL_V(xr_server, transform_for_eye); + ERR_FAIL_NULL_V(xr_server, camera_transform); if (initialized) { float world_scale = xr_server->get_world_scale(); @@ -345,181 +353,382 @@ Transform3D WebXRInterfaceJS::get_camera_transform() { Transform3D _head_transform = head_transform; _head_transform.origin *= world_scale; - transform_for_eye = (xr_server->get_reference_frame()) * _head_transform; + camera_transform = (xr_server->get_reference_frame()) * _head_transform; } - return transform_for_eye; + return camera_transform; }; Transform3D WebXRInterfaceJS::get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) { - Transform3D transform_for_eye; - XRServer *xr_server = XRServer::get_singleton(); - ERR_FAIL_NULL_V(xr_server, transform_for_eye); + ERR_FAIL_NULL_V(xr_server, p_cam_transform); + ERR_FAIL_COND_V(!initialized, p_cam_transform); - float *js_matrix = godot_webxr_get_transform_for_eye(p_view + 1); - if (!initialized || js_matrix == nullptr) { - transform_for_eye = p_cam_transform; - return transform_for_eye; + float js_matrix[16]; + bool has_transform = godot_webxr_get_transform_for_view(p_view, js_matrix); + if (!has_transform) { + return p_cam_transform; } - transform_for_eye = _js_matrix_to_transform(js_matrix); - free(js_matrix); + Transform3D transform_for_view = _js_matrix_to_transform(js_matrix); float world_scale = xr_server->get_world_scale(); // Scale only the center point of our eye transform, so we don't scale the // distance between the eyes. Transform3D _head_transform = head_transform; - transform_for_eye.origin -= _head_transform.origin; + transform_for_view.origin -= _head_transform.origin; _head_transform.origin *= world_scale; - transform_for_eye.origin += _head_transform.origin; + transform_for_view.origin += _head_transform.origin; - return p_cam_transform * xr_server->get_reference_frame() * transform_for_eye; + return p_cam_transform * xr_server->get_reference_frame() * transform_for_view; }; Projection WebXRInterfaceJS::get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) { - Projection eye; + Projection view; + + ERR_FAIL_COND_V(!initialized, view); - float *js_matrix = godot_webxr_get_projection_for_eye(p_view + 1); - if (!initialized || js_matrix == nullptr) { - return eye; + float js_matrix[16]; + bool has_projection = godot_webxr_get_projection_for_view(p_view, js_matrix); + if (!has_projection) { + return view; } int k = 0; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { - eye.columns[i][j] = js_matrix[k++]; + view.columns[i][j] = js_matrix[k++]; } } - free(js_matrix); - // Copied from godot_oculus_mobile's ovr_mobile_session.cpp - eye.columns[2][2] = -(p_z_far + p_z_near) / (p_z_far - p_z_near); - eye.columns[3][2] = -(2.0f * p_z_far * p_z_near) / (p_z_far - p_z_near); + view.columns[2][2] = -(p_z_far + p_z_near) / (p_z_far - p_z_near); + view.columns[3][2] = -(2.0f * p_z_far * p_z_near) / (p_z_far - p_z_near); - return eye; + return view; +} + +bool WebXRInterfaceJS::pre_draw_viewport(RID p_render_target) { + GLES3::TextureStorage *texture_storage = dynamic_cast<GLES3::TextureStorage *>(RSG::texture_storage); + if (texture_storage == nullptr) { + return false; + } + + GLES3::RenderTarget *rt = texture_storage->get_render_target(p_render_target); + if (rt == nullptr) { + return false; + } + + // Cache the resources so we don't have to get them from JS twice. + color_texture = _get_color_texture(); + depth_texture = _get_depth_texture(); + + // Per the WebXR spec, it returns "opaque textures" to us, which may be the + // same WebGLTexture object (which would be the same GLuint in C++) but + // represent a different underlying resource (probably the next texture in + // the XR device's swap chain). In order to render to this texture, we need + // to re-attach it to the FBO, otherwise we get an "incomplete FBO" error. + // + // See: https://immersive-web.github.io/layers/#xropaquetextures + // + // This is why we're doing this sort of silly check: if the color and depth + // textures are the same this frame as last frame, we need to attach them + // again, despite the fact that the GLuint for them hasn't changed. + if (rt->overridden.is_overridden && rt->overridden.color == color_texture && rt->overridden.depth == depth_texture) { + GLES3::Config *config = GLES3::Config::get_singleton(); + bool use_multiview = rt->view_count > 1 && config->multiview_supported; + + glBindFramebuffer(GL_FRAMEBUFFER, rt->fbo); + if (use_multiview) { + glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, rt->color, 0, 0, rt->view_count); + glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, rt->depth, 0, 0, rt->view_count); + } else { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rt->color, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, rt->depth, 0); + } + glBindFramebuffer(GL_FRAMEBUFFER, texture_storage->system_fbo); + } + + return true; } Vector<BlitToScreen> WebXRInterfaceJS::post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) { Vector<BlitToScreen> blit_to_screen; - if (!initialized) { - return blit_to_screen; + // We don't need to do anything here. + + return blit_to_screen; +}; + +RID WebXRInterfaceJS::_get_color_texture() { + unsigned int texture_id = godot_webxr_get_color_texture(); + if (texture_id == 0) { + return RID(); + } + + return _get_texture(texture_id); +} + +RID WebXRInterfaceJS::_get_depth_texture() { + unsigned int texture_id = godot_webxr_get_depth_texture(); + if (texture_id == 0) { + return RID(); + } + + return _get_texture(texture_id); +} + +RID WebXRInterfaceJS::_get_texture(unsigned int p_texture_id) { + RBMap<unsigned int, RID>::Element *cache = texture_cache.find(p_texture_id); + if (cache != nullptr) { + return cache->get(); } GLES3::TextureStorage *texture_storage = dynamic_cast<GLES3::TextureStorage *>(RSG::texture_storage); - if (!texture_storage) { - return blit_to_screen; + if (texture_storage == nullptr) { + return RID(); } - GLES3::RenderTarget *rt = texture_storage->get_render_target(p_render_target); + uint32_t view_count = godot_webxr_get_view_count(); + Size2 texture_size = get_render_target_size(); - godot_webxr_commit(rt->color); + RID texture = texture_storage->texture_create_external( + view_count == 1 ? GLES3::Texture::TYPE_2D : GLES3::Texture::TYPE_LAYERED, + Image::FORMAT_RGBA8, + p_texture_id, + (int)texture_size.width, + (int)texture_size.height, + 1, + view_count); - return blit_to_screen; -}; + texture_cache.insert(p_texture_id, texture); + + return texture; +} + +RID WebXRInterfaceJS::get_color_texture() { + return color_texture; +} + +RID WebXRInterfaceJS::get_depth_texture() { + return depth_texture; +} + +RID WebXRInterfaceJS::get_velocity_texture() { + unsigned int texture_id = godot_webxr_get_velocity_texture(); + if (texture_id == 0) { + return RID(); + } + + return _get_texture(texture_id); +} void WebXRInterfaceJS::process() { if (initialized) { // Get the "head" position. - float *js_matrix = godot_webxr_get_transform_for_eye(0); - if (js_matrix != nullptr) { + float js_matrix[16]; + if (godot_webxr_get_transform_for_view(-1, js_matrix)) { head_transform = _js_matrix_to_transform(js_matrix); - free(js_matrix); } if (head_tracker.is_valid()) { head_tracker->set_pose("default", head_transform, Vector3(), Vector3()); } - godot_webxr_sample_controller_data(); - int controller_count = godot_webxr_get_controller_count(); - for (int i = 0; i < controller_count; i++) { - _update_tracker(i); + // Update all input sources. + for (int i = 0; i < input_source_count; i++) { + _update_input_source(i); } }; }; -void WebXRInterfaceJS::_update_tracker(int p_controller_id) { +void WebXRInterfaceJS::_update_input_source(int p_input_source_id) { XRServer *xr_server = XRServer::get_singleton(); ERR_FAIL_NULL(xr_server); - // need to support more then two controllers... - if (p_controller_id < 0 || p_controller_id > 1) { + InputSource &input_source = input_sources[p_input_source_id]; + + float target_pose[16]; + int tmp_target_ray_mode; + int touch_index; + int has_grip_pose; + float grip_pose[16]; + int has_standard_mapping; + int button_count; + float buttons[10]; + int axes_count; + float axes[10]; + + input_source.active = godot_webxr_update_input_source( + p_input_source_id, + target_pose, + &tmp_target_ray_mode, + &touch_index, + &has_grip_pose, + grip_pose, + &has_standard_mapping, + &button_count, + buttons, + &axes_count, + axes); + + if (!input_source.active) { + if (input_source.tracker.is_valid()) { + xr_server->remove_tracker(input_source.tracker); + input_source.tracker.unref(); + } return; } - Ref<XRPositionalTracker> tracker = controllers[p_controller_id]; - if (godot_webxr_is_controller_connected(p_controller_id)) { - if (tracker.is_null()) { - tracker.instantiate(); - tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); - // Controller id's 0 and 1 are always the left and right hands. - if (p_controller_id < 2) { - tracker->set_tracker_name(p_controller_id == 0 ? "left_hand" : "right_hand"); - tracker->set_tracker_desc(p_controller_id == 0 ? "Left hand controller" : "Right hand controller"); - tracker->set_tracker_hand(p_controller_id == 0 ? XRPositionalTracker::TRACKER_HAND_LEFT : XRPositionalTracker::TRACKER_HAND_RIGHT); - } else { - char name[1024]; - sprintf(name, "tracker_%i", p_controller_id); - tracker->set_tracker_name(name); - tracker->set_tracker_desc(name); - } - xr_server->add_tracker(tracker); + input_source.target_ray_mode = (WebXRInterface::TargetRayMode)tmp_target_ray_mode; + input_source.touch_index = touch_index; + + Ref<XRPositionalTracker> &tracker = input_source.tracker; + + if (tracker.is_null()) { + tracker.instantiate(); + + StringName tracker_name; + if (input_source.target_ray_mode == WebXRInterface::TargetRayMode::TARGET_RAY_MODE_SCREEN) { + tracker_name = touch_names[touch_index]; + } else { + tracker_name = tracker_names[p_input_source_id]; } - float *tracker_matrix = godot_webxr_get_controller_transform(p_controller_id); - if (tracker_matrix) { - // Note, poses should NOT have world scale and our reference frame applied! - Transform3D transform = _js_matrix_to_transform(tracker_matrix); - tracker->set_pose("default", transform, Vector3(), Vector3()); - free(tracker_matrix); + // Input source id's 0 and 1 are always the left and right hands. + if (p_input_source_id < 2) { + tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); + tracker->set_tracker_name(tracker_name); + tracker->set_tracker_desc(p_input_source_id == 0 ? "Left hand controller" : "Right hand controller"); + tracker->set_tracker_hand(p_input_source_id == 0 ? XRPositionalTracker::TRACKER_HAND_LEFT : XRPositionalTracker::TRACKER_HAND_RIGHT); + } else { + tracker->set_tracker_name(tracker_name); + tracker->set_tracker_desc(tracker_name); } + xr_server->add_tracker(tracker); + } - // TODO implement additional poses such as "aim" and "grip" + Transform3D aim_transform = _js_matrix_to_transform(target_pose); + tracker->set_pose(SNAME("default"), aim_transform, Vector3(), Vector3()); + tracker->set_pose(SNAME("aim"), aim_transform, Vector3(), Vector3()); + if (has_grip_pose) { + tracker->set_pose(SNAME("grip"), _js_matrix_to_transform(grip_pose), Vector3(), Vector3()); + } - int *buttons = godot_webxr_get_controller_buttons(p_controller_id); - if (buttons) { - // TODO buttons should be named properly, this is just a temporary fix - for (int i = 0; i < buttons[0]; i++) { - char name[1024]; - sprintf(name, "button_%i", i); + for (int i = 0; i < button_count; i++) { + StringName button_name = has_standard_mapping ? standard_button_names[i] : unknown_button_names[i]; + StringName button_pressure_name = has_standard_mapping ? standard_button_pressure_names[i] : unknown_button_pressure_names[i]; + float value = buttons[i]; + bool state = value > 0.0; + tracker->set_input(button_name, state); + tracker->set_input(button_pressure_name, value); + } - float value = *((float *)buttons + (i + 1)); - bool state = value > 0.0; - tracker->set_input(name, state); - } - free(buttons); + for (int i = 0; i < axes_count; i++) { + StringName axis_name = has_standard_mapping ? standard_axis_names[i] : unknown_axis_names[i]; + float value = axes[i]; + if (has_standard_mapping && (i == 1 || i == 3)) { + // Invert the Y-axis on thumbsticks and trackpads, in order to + // match OpenXR and other XR platform SDKs. + value = -value; } + tracker->set_input(axis_name, value); + } - int *axes = godot_webxr_get_controller_axes(p_controller_id); - if (axes) { - // TODO again just a temporary fix, split these between proper float and vector2 inputs - for (int i = 0; i < axes[0]; i++) { - char name[1024]; - sprintf(name, "axis_%i", i); + // Also create Vector2's for the thumbstick and trackpad when we have the + // standard mapping. + if (has_standard_mapping) { + if (axes_count >= 2) { + tracker->set_input(standard_vector_names[0], Vector2(axes[0], -axes[1])); + } + if (axes_count >= 4) { + tracker->set_input(standard_vector_names[1], Vector2(axes[2], -axes[3])); + } + } - float value = *((float *)axes + (i + 1)); - tracker->set_input(name, value); + if (input_source.target_ray_mode == WebXRInterface::TARGET_RAY_MODE_SCREEN) { + if (touch_index < 5 && axes_count >= 2) { + Vector2 joy_vector = Vector2(axes[0], axes[1]); + Vector2 position = _get_screen_position_from_joy_vector(joy_vector); + + if (touches[touch_index].is_touching) { + Vector2 delta = position - touches[touch_index].position; + + // If position has changed by at least 1 pixel, generate a drag event. + if (abs(delta.x) >= 1.0 || abs(delta.y) >= 1.0) { + Ref<InputEventScreenDrag> event; + event.instantiate(); + event->set_index(touch_index); + event->set_position(position); + event->set_relative(delta); + Input::get_singleton()->parse_input_event(event); + } } - free(axes); + + touches[touch_index].position = position; } - } else if (tracker.is_valid()) { - xr_server->remove_tracker(tracker); - controllers[p_controller_id].unref(); } } -void WebXRInterfaceJS::_on_controller_changed() { - // Register "virtual" gamepads with Godot for the ones we get from WebXR. - godot_webxr_sample_controller_data(); - for (int i = 0; i < 2; i++) { - bool controller_connected = godot_webxr_is_controller_connected(i); - if (controllers_state[i] != controller_connected) { - // Input::get_singleton()->joy_connection_changed(i + 100, controller_connected, i == 0 ? "Left" : "Right", ""); - controllers_state[i] = controller_connected; +void WebXRInterfaceJS::_on_input_event(int p_event_type, int p_input_source_id) { + // Get the latest data for this input source. For transient input sources, + // we may not have any data at all yet! + _update_input_source(p_input_source_id); + + if (p_event_type == WEBXR_INPUT_EVENT_SELECTSTART || p_event_type == WEBXR_INPUT_EVENT_SELECTEND) { + const InputSource &input_source = input_sources[p_input_source_id]; + if (input_source.target_ray_mode == WebXRInterface::TARGET_RAY_MODE_SCREEN) { + int touch_index = input_source.touch_index; + if (touch_index >= 0 && touch_index < 5) { + touches[touch_index].is_touching = (p_event_type == WEBXR_INPUT_EVENT_SELECTSTART); + + Ref<InputEventScreenTouch> event; + event.instantiate(); + event->set_index(touch_index); + event->set_position(touches[touch_index].position); + event->set_pressed(p_event_type == WEBXR_INPUT_EVENT_SELECTSTART); + + Input::get_singleton()->parse_input_event(event); + } } } + + switch (p_event_type) { + case WEBXR_INPUT_EVENT_SELECTSTART: + emit_signal("selectstart", p_input_source_id); + break; + + case WEBXR_INPUT_EVENT_SELECTEND: + emit_signal("selectend", p_input_source_id); + // Emit the 'select' event on our own (rather than intercepting the + // one from JavaScript) so that we don't have to needlessly call + // _update_input_source() a second time. + emit_signal("select", p_input_source_id); + break; + + case WEBXR_INPUT_EVENT_SQUEEZESTART: + emit_signal("squeezestart", p_input_source_id); + break; + + case WEBXR_INPUT_EVENT_SQUEEZEEND: + emit_signal("squeezeend", p_input_source_id); + // Again, we emit the 'squeeze' event on our own to avoid extra work. + emit_signal("squeeze", p_input_source_id); + break; + } +} + +Vector2 WebXRInterfaceJS::_get_screen_position_from_joy_vector(const Vector2 &p_joy_vector) { + SceneTree *scene_tree = Object::cast_to<SceneTree>(OS::get_singleton()->get_main_loop()); + if (!scene_tree) { + return Vector2(); + } + + Window *viewport = scene_tree->get_root(); + + Vector2 position_percentage((p_joy_vector.x + 1.0f) / 2.0f, ((p_joy_vector.y) + 1.0f) / 2.0f); + Vector2 position = (Size2)viewport->get_size() * position_percentage; + + return position; } WebXRInterfaceJS::WebXRInterfaceJS() { diff --git a/modules/webxr/webxr_interface_js.h b/modules/webxr/webxr_interface_js.h index 319adc2ac9..6b484a8872 100644 --- a/modules/webxr/webxr_interface_js.h +++ b/modules/webxr/webxr_interface_js.h @@ -39,6 +39,10 @@ The WebXR interface is a VR/AR interface that can be used on the web. */ +namespace GLES3 { +class TextureStorage; +} + class WebXRInterfaceJS : public WebXRInterface { GDCLASS(WebXRInterfaceJS, WebXRInterface); @@ -53,13 +57,32 @@ private: String requested_reference_space_types; String reference_space_type; - // TODO maybe turn into a vector to support more then 2 controllers... - bool controllers_state[2]; - Ref<XRPositionalTracker> controllers[2]; Size2 render_targetsize; - + RBMap<unsigned int, RID> texture_cache; + struct Touch { + bool is_touching = false; + Vector2 position; + } touches[5]; + + static constexpr uint8_t input_source_count = 16; + + struct InputSource { + Ref<XRPositionalTracker> tracker; + bool active = false; + TargetRayMode target_ray_mode; + int touch_index = -1; + } input_sources[input_source_count]; + + RID color_texture; + RID depth_texture; + + RID _get_color_texture(); + RID _get_depth_texture(); + RID _get_texture(unsigned int p_texture_id); Transform3D _js_matrix_to_transform(float *p_js_matrix); - void _update_tracker(int p_controller_id); + void _update_input_source(int p_input_source_id); + + Vector2 _get_screen_position_from_joy_vector(const Vector2 &p_joy_vector); public: virtual void is_session_supported(const String &p_session_mode) override; @@ -73,9 +96,11 @@ public: virtual String get_requested_reference_space_types() const override; void _set_reference_space_type(String p_reference_space_type); virtual String get_reference_space_type() const override; - virtual Ref<XRPositionalTracker> get_controller(int p_controller_id) const override; + virtual bool is_input_source_active(int p_input_source_id) const override; + virtual Ref<XRPositionalTracker> get_input_source_tracker(int p_input_source_id) const override; + virtual TargetRayMode get_input_source_target_ray_mode(int p_input_source_id) const override; virtual String get_visibility_state() const override; - virtual PackedVector3Array get_bounds_geometry() const override; + virtual PackedVector3Array get_play_area() const override; virtual StringName get_name() const override; virtual uint32_t get_capabilities() const override; @@ -89,14 +114,129 @@ public: virtual Transform3D get_camera_transform() override; virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override; virtual Projection get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) override; + virtual bool pre_draw_viewport(RID p_render_target) override; virtual Vector<BlitToScreen> post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) override; + virtual RID get_color_texture() override; + virtual RID get_depth_texture() override; + virtual RID get_velocity_texture() override; virtual void process() override; - void _on_controller_changed(); + void _on_input_event(int p_event_type, int p_input_source_id); WebXRInterfaceJS(); ~WebXRInterfaceJS(); + +private: + StringName tracker_names[16] = { + StringName("left_hand"), + StringName("right_hand"), + StringName("tracker_2"), + StringName("tracker_3"), + StringName("tracker_4"), + StringName("tracker_5"), + StringName("tracker_6"), + StringName("tracker_7"), + StringName("tracker_8"), + StringName("tracker_9"), + StringName("tracker_10"), + StringName("tracker_11"), + StringName("tracker_12"), + StringName("tracker_13"), + StringName("tracker_14"), + StringName("tracker_15"), + }; + + StringName touch_names[5] = { + StringName("touch_0"), + StringName("touch_1"), + StringName("touch_2"), + StringName("touch_3"), + StringName("touch_4"), + }; + + StringName standard_axis_names[10] = { + StringName("touchpad_x"), + StringName("touchpad_y"), + StringName("thumbstick_x"), + StringName("thumbstick_y"), + StringName("axis_4"), + StringName("axis_5"), + StringName("axis_6"), + StringName("axis_7"), + StringName("axis_8"), + StringName("axis_9"), + }; + + StringName standard_vector_names[2] = { + StringName("touchpad"), + StringName("thumbstick"), + }; + + StringName standard_button_names[10] = { + StringName("trigger_click"), + StringName("grip_click"), + StringName("touchpad_click"), + StringName("thumbstick_click"), + StringName("ax_button"), + StringName("by_button"), + StringName("button_6"), + StringName("button_7"), + StringName("button_8"), + StringName("button_9"), + }; + + StringName standard_button_pressure_names[10] = { + StringName("trigger"), + StringName("grip"), + StringName("touchpad_click_pressure"), + StringName("thumbstick_click_pressure"), + StringName("ax_button_pressure"), + StringName("by_button_pressure"), + StringName("button_pressure_6"), + StringName("button_pressure_7"), + StringName("button_pressure_8"), + StringName("button_pressure_9"), + }; + + StringName unknown_button_names[10] = { + StringName("button_0"), + StringName("button_1"), + StringName("button_2"), + StringName("button_3"), + StringName("button_4"), + StringName("button_5"), + StringName("button_6"), + StringName("button_7"), + StringName("button_8"), + StringName("button_9"), + }; + + StringName unknown_axis_names[10] = { + StringName("axis_0"), + StringName("axis_1"), + StringName("axis_2"), + StringName("axis_3"), + StringName("axis_4"), + StringName("axis_5"), + StringName("axis_6"), + StringName("axis_7"), + StringName("axis_8"), + StringName("axis_9"), + }; + + StringName unknown_button_pressure_names[10] = { + StringName("button_pressure_0"), + StringName("button_pressure_1"), + StringName("button_pressure_2"), + StringName("button_pressure_3"), + StringName("button_pressure_4"), + StringName("button_pressure_5"), + StringName("button_pressure_6"), + StringName("button_pressure_7"), + StringName("button_pressure_8"), + StringName("button_pressure_9"), + }; }; #endif // WEB_ENABLED diff --git a/modules/zip/doc_classes/ZIPReader.xml b/modules/zip/doc_classes/ZIPReader.xml index 717116a531..055201b105 100644 --- a/modules/zip/doc_classes/ZIPReader.xml +++ b/modules/zip/doc_classes/ZIPReader.xml @@ -9,7 +9,7 @@ func read_zip_file(): var reader := ZIPReader.new() var err := reader.open("user://archive.zip") - if err == OK: + if err != OK: return PackedByteArray() var res := reader.read_file("hello.txt") reader.close() diff --git a/modules/zip/zip_packer.cpp b/modules/zip/zip_packer.cpp index e62700f191..5566848087 100644 --- a/modules/zip/zip_packer.cpp +++ b/modules/zip/zip_packer.cpp @@ -46,7 +46,11 @@ Error ZIPPacker::open(String p_path, ZipAppend p_append) { Error ZIPPacker::close() { ERR_FAIL_COND_V_MSG(fa.is_null(), FAILED, "ZIPPacker cannot be closed because it is not open."); - return zipClose(zf, NULL) == ZIP_OK ? OK : FAILED; + Error err = zipClose(zf, NULL) == ZIP_OK ? OK : FAILED; + if (err == OK) { + zf = NULL; + } + return err; } Error ZIPPacker::start_file(String p_path) { @@ -79,11 +83,7 @@ Error ZIPPacker::write_file(Vector<uint8_t> p_data) { Error ZIPPacker::close_file() { ERR_FAIL_COND_V_MSG(fa.is_null(), FAILED, "ZIPPacker must be opened before use."); - Error err = zipCloseFileInZip(zf) == ZIP_OK ? OK : FAILED; - if (err == OK) { - zf = NULL; - } - return err; + return zipCloseFileInZip(zf) == ZIP_OK ? OK : FAILED; } void ZIPPacker::_bind_methods() { |