diff options
Diffstat (limited to 'modules')
51 files changed, 567 insertions, 214 deletions
diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp index bde23289f4..44a929d285 100644 --- a/modules/fbx/fbx_document.cpp +++ b/modules/fbx/fbx_document.cpp @@ -1629,6 +1629,9 @@ void FBXDocument::_generate_skeleton_bone_node(Ref<FBXState> p_state, const GLTF active_skeleton = skeleton; current_node = active_skeleton; + if (active_skeleton) { + p_scene_parent = active_skeleton; + } if (requires_extra_node) { current_node = nullptr; @@ -2019,8 +2022,8 @@ Node *FBXDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool GLTFNodeIndex fbx_root = state->root_nodes.write[0]; Node *fbx_root_node = state->get_scene_node(fbx_root); Node *root = fbx_root_node; - if (fbx_root_node && fbx_root_node->get_parent()) { - root = fbx_root_node->get_parent(); + if (root && root->get_owner() && root->get_owner() != root) { + root = root->get_owner(); } ERR_FAIL_NULL_V(root, nullptr); _process_mesh_instances(state, root); diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index b198338ff0..ec75663e97 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -1957,6 +1957,18 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } else { parser->push_warning(p_assignable, GDScriptWarning::UNTYPED_DECLARATION, declaration_type, p_assignable->identifier->name); } + } else if (specified_type.kind == GDScriptParser::DataType::ENUM && p_assignable->initializer == nullptr) { + // Warn about enum variables without default value. Unless the enum defines the "0" value, then it's fine. + bool has_zero_value = false; + for (const KeyValue<StringName, int64_t> &kv : specified_type.enum_values) { + if (kv.value == 0) { + has_zero_value = true; + break; + } + } + if (!has_zero_value) { + parser->push_warning(p_assignable, GDScriptWarning::ENUM_VARIABLE_WITHOUT_DEFAULT, p_assignable->identifier->name); + } } #endif @@ -1985,7 +1997,7 @@ void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant #ifdef DEBUG_ENABLED if (p_is_local) { - if (p_constant->usages == 0) { + if (p_constant->usages == 0 && !String(p_constant->identifier->name).begins_with("_")) { parser->push_warning(p_constant, GDScriptWarning::UNUSED_LOCAL_CONSTANT, p_constant->identifier->name); } } diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index d706c1b101..c526d9c0a4 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -250,7 +250,7 @@ static bool _can_use_validate_call(const MethodBind *p_method, const Vector<GDSc return true; } -GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer, const GDScriptCodeGenerator::Address &p_index_addr) { +GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer) { if (p_expression->is_constant && !(p_expression->get_datatype().is_meta_type && p_expression->get_datatype().kind == GDScriptParser::DataType::CLASS)) { return codegen.add_constant(p_expression->reduced_value); } @@ -781,9 +781,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code bool named = subscript->is_attribute; StringName name; GDScriptCodeGenerator::Address index; - if (p_index_addr.mode != GDScriptCodeGenerator::Address::NIL) { - index = p_index_addr; - } else if (subscript->is_attribute) { + if (subscript->is_attribute) { if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script) { GDScriptParser::IdentifierNode *identifier = subscript->attribute; HashMap<StringName, GDScript::MemberInfo>::Iterator MI = codegen.script->member_indices.find(identifier->name); diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 0adbe1ed8e..637d61ca3b 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -149,13 +149,9 @@ class GDScriptCompiler { 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, bool p_handle_metatype = true); - 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()); + GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false); GDScriptCodeGenerator::Address _parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested); List<GDScriptCodeGenerator::Address> _add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block); void _clear_addresses(CodeGen &codegen, const List<GDScriptCodeGenerator::Address> &p_addresses); diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 2940af585d..5b1639e250 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -1455,10 +1455,11 @@ GDScriptTokenizer::Token GDScriptTokenizerText::scan() { if (_peek() != '\n') { return make_error("Expected new line after \"\\\"."); } - continuation_lines.push_back(line); _advance(); newline(false); line_continuation = true; + _skip_whitespace(); // Skip whitespace/comment lines after `\`. See GH-89403. + continuation_lines.push_back(line); return scan(); // Recurse to get next token. } diff --git a/modules/gdscript/gdscript_tokenizer_buffer.cpp b/modules/gdscript/gdscript_tokenizer_buffer.cpp index db523ea941..e53bc5bc41 100644 --- a/modules/gdscript/gdscript_tokenizer_buffer.cpp +++ b/modules/gdscript/gdscript_tokenizer_buffer.cpp @@ -285,9 +285,9 @@ Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code, // Remove continuation lines from map. for (int line : tokenizer.get_continuation_lines()) { - if (rev_token_lines.has(line + 1)) { - token_lines.erase(rev_token_lines[line + 1]); - token_columns.erase(rev_token_lines[line + 1]); + if (rev_token_lines.has(line)) { + token_lines.erase(rev_token_lines[line]); + token_columns.erase(rev_token_lines[line]); } } diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index 708966a0a8..48a0abe617 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -126,6 +126,9 @@ String GDScriptWarning::get_message() const { case INT_AS_ENUM_WITHOUT_MATCH: CHECK_SYMBOLS(3); return vformat(R"(Cannot %s %s as Enum "%s": no enum member has matching value.)", symbols[0], symbols[1], symbols[2]); + case ENUM_VARIABLE_WITHOUT_DEFAULT: + CHECK_SYMBOLS(1); + return vformat(R"(The variable "%s" has an enum type and does not set an explicit default value. The default will be set to "0".)", symbols[0]); case EMPTY_FILE: return "Empty script file."; case DEPRECATED_KEYWORD: @@ -221,6 +224,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "NARROWING_CONVERSION", "INT_AS_ENUM_WITHOUT_CAST", "INT_AS_ENUM_WITHOUT_MATCH", + "ENUM_VARIABLE_WITHOUT_DEFAULT", "EMPTY_FILE", "DEPRECATED_KEYWORD", "RENAMED_IN_GODOT_4_HINT", diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index 93c232a0f8..3ad9488138 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -78,6 +78,7 @@ public: NARROWING_CONVERSION, // Float value into an integer slot, precision is lost. INT_AS_ENUM_WITHOUT_CAST, // An integer value was used as an enum value without casting. INT_AS_ENUM_WITHOUT_MATCH, // An integer value was used as an enum value without matching enum member. + ENUM_VARIABLE_WITHOUT_DEFAULT, // A variable with an enum type does not have a default value. The default will be set to `0` instead of the first enum value. EMPTY_FILE, // A script file is empty. DEPRECATED_KEYWORD, // The keyword is deprecated and should be replaced. RENAMED_IN_GODOT_4_HINT, // A variable or function that could not be found has been renamed in Godot 4. @@ -129,6 +130,7 @@ public: WARN, // NARROWING_CONVERSION WARN, // INT_AS_ENUM_WITHOUT_CAST WARN, // INT_AS_ENUM_WITHOUT_MATCH + WARN, // ENUM_VARIABLE_WITHOUT_DEFAULT WARN, // EMPTY_FILE WARN, // DEPRECATED_KEYWORD WARN, // RENAMED_IN_GODOT_4_HINT diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index a0329eb8d2..e3d16eaf42 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -300,14 +300,23 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) { #endif String out_file = next.get_basename() + ".out"; - if (!is_generating && !dir->file_exists(out_file)) { - ERR_FAIL_V_MSG(false, "Could not find output file for " + next); - } - GDScriptTest test(current_dir.path_join(next), current_dir.path_join(out_file), source_dir); - if (binary_tokens) { - test.set_tokenizer_mode(GDScriptTest::TOKENIZER_BUFFER); + ERR_FAIL_COND_V_MSG(!is_generating && !dir->file_exists(out_file), false, "Could not find output file for " + next); + + if (next.ends_with(".bin.gd")) { + // Test text mode first. + GDScriptTest text_test(current_dir.path_join(next), current_dir.path_join(out_file), source_dir); + tests.push_back(text_test); + // Test binary mode even without `--use-binary-tokens`. + GDScriptTest bin_test(current_dir.path_join(next), current_dir.path_join(out_file), source_dir); + bin_test.set_tokenizer_mode(GDScriptTest::TOKENIZER_BUFFER); + tests.push_back(bin_test); + } else { + GDScriptTest test(current_dir.path_join(next), current_dir.path_join(out_file), source_dir); + if (binary_tokens) { + test.set_tokenizer_mode(GDScriptTest::TOKENIZER_BUFFER); + } + tests.push_back(test); } - tests.push_back(test); } } diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd b/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd new file mode 100644 index 0000000000..13e3edf93f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd @@ -0,0 +1,9 @@ +enum HasZero { A = 0, B = 1 } +enum HasNoZero { A = 1, B = 2 } +var has_zero: HasZero # No warning, because the default `0` is valid. +var has_no_zero: HasNoZero # Warning, because there is no `0` in the enum. + + +func test(): + print(has_zero) + print(has_no_zero) diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.out b/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.out new file mode 100644 index 0000000000..ae40e0bc8c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.out @@ -0,0 +1,7 @@ +GDTEST_OK +>> WARNING +>> Line: 4 +>> ENUM_VARIABLE_WITHOUT_DEFAULT +>> The variable "has_no_zero" has an enum type and does not set an explicit default value. The default will be set to "0". +0 +0 diff --git a/modules/gdscript/tests/scripts/parser/features/constants.out b/modules/gdscript/tests/scripts/parser/features/constants.out index 7ec33470d3..d73c5eb7cd 100644 --- a/modules/gdscript/tests/scripts/parser/features/constants.out +++ b/modules/gdscript/tests/scripts/parser/features/constants.out @@ -1,33 +1 @@ GDTEST_OK ->> WARNING ->> Line: 2 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_TEST" is declared but never used in the block. If this is intended, prefix it with an underscore: "__TEST". ->> WARNING ->> Line: 3 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_STRING" is declared but never used in the block. If this is intended, prefix it with an underscore: "__STRING". ->> WARNING ->> Line: 4 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_VECTOR" is declared but never used in the block. If this is intended, prefix it with an underscore: "__VECTOR". ->> WARNING ->> Line: 5 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_ARRAY" is declared but never used in the block. If this is intended, prefix it with an underscore: "__ARRAY". ->> WARNING ->> Line: 6 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_DICTIONARY" is declared but never used in the block. If this is intended, prefix it with an underscore: "__DICTIONARY". ->> WARNING ->> Line: 9 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_HELLO" is declared but never used in the block. If this is intended, prefix it with an underscore: "__HELLO". ->> WARNING ->> Line: 10 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_INFINITY" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INFINITY". ->> WARNING ->> Line: 11 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_NOT_A_NUMBER" is declared but never used in the block. If this is intended, prefix it with an underscore: "__NOT_A_NUMBER". diff --git a/modules/gdscript/tests/scripts/parser/features/continuation_lines_comments.bin.gd b/modules/gdscript/tests/scripts/parser/features/continuation_lines_comments.bin.gd new file mode 100644 index 0000000000..cb0bc94d2e --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/continuation_lines_comments.bin.gd @@ -0,0 +1,12 @@ +# GH-89403 + +func test(): + var x := 1 + if x == 0 \ + # Comment. + # Comment. + and (x < 1 or x > 2) \ + # Comment. + and x != 3: + pass + print("Ok") diff --git a/modules/gdscript/tests/scripts/parser/features/continuation_lines_comments.bin.out b/modules/gdscript/tests/scripts/parser/features/continuation_lines_comments.bin.out new file mode 100644 index 0000000000..0e9f482af4 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/continuation_lines_comments.bin.out @@ -0,0 +1,2 @@ +GDTEST_OK +Ok diff --git a/modules/gdscript/tests/scripts/parser/features/static_typing.out b/modules/gdscript/tests/scripts/parser/features/static_typing.out index 40a8f97416..d73c5eb7cd 100644 --- a/modules/gdscript/tests/scripts/parser/features/static_typing.out +++ b/modules/gdscript/tests/scripts/parser/features/static_typing.out @@ -1,21 +1 @@ GDTEST_OK ->> WARNING ->> Line: 11 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_INTEGER" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER". ->> WARNING ->> Line: 12 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_INTEGER_REDUNDANT_TYPED" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_TYPED". ->> WARNING ->> Line: 13 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_INTEGER_REDUNDANT_TYPED2" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_TYPED2". ->> WARNING ->> Line: 14 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_INTEGER_REDUNDANT_INFERRED" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_INFERRED". ->> WARNING ->> Line: 15 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_INTEGER_REDUNDANT_INFERRED2" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_INFERRED2". diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_constant.gd b/modules/gdscript/tests/scripts/parser/warnings/unused_constant.gd new file mode 100644 index 0000000000..3d355197e1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unused_constant.gd @@ -0,0 +1,4 @@ +func test(): + const UNUSED = "not used" + + const _UNUSED = "not used, but no warning since the constant name starts with an underscore" diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_constant.out b/modules/gdscript/tests/scripts/parser/warnings/unused_constant.out new file mode 100644 index 0000000000..99ced48433 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unused_constant.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 2 +>> UNUSED_LOCAL_CONSTANT +>> The local constant "UNUSED" is declared but never used in the block. If this is intended, prefix it with an underscore: "_UNUSED". diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.gd b/modules/gdscript/tests/scripts/runtime/features/member_info.gd index d7485f49e6..42b29eee43 100644 --- a/modules/gdscript/tests/scripts/runtime/features/member_info.gd +++ b/modules/gdscript/tests/scripts/runtime/features/member_info.gd @@ -23,6 +23,7 @@ var test_var_hard_int: int var test_var_hard_variant_type: Variant.Type @export var test_var_hard_variant_type_exported: Variant.Type var test_var_hard_node_process_mode: Node.ProcessMode +@warning_ignore("enum_variable_without_default") var test_var_hard_my_enum: MyEnum var test_var_hard_array: Array var test_var_hard_array_int: Array[int] diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index f8c35ab6d1..8f0f0d219e 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -5685,6 +5685,9 @@ void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> p_state, const GL active_skeleton = skeleton; current_node = active_skeleton; + if (active_skeleton) { + p_scene_parent = active_skeleton; + } if (requires_extra_node) { current_node = nullptr; @@ -7104,6 +7107,9 @@ Node *GLTFDocument::_generate_scene_node_tree(Ref<GLTFState> p_state) { if (p_state->extensions_used.has("GODOT_single_root")) { _generate_scene_node(p_state, 0, nullptr, nullptr); single_root = p_state->scene_nodes[0]; + if (single_root && single_root->get_owner() && single_root->get_owner() != single_root) { + single_root = single_root->get_owner(); + } } else { single_root = memnew(Node3D); for (int32_t root_i = 0; root_i < p_state->root_nodes.size(); root_i++) { diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index b6f5d6ce57..835fb3e59d 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -1908,7 +1908,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d seams_push_constant.slice = uint32_t(i * subslices + k); seams_push_constant.debug = debug; - RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); + RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i * subslices + k], RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0); rd->draw_list_bind_uniform_set(draw_list, blendseams_raster_uniform, 1); diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index a3464ccfc2..dcc18ebdd7 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -941,6 +941,31 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { to_reload_state.push_back(scr); } + // Deserialize managed callables. + // This is done before reloading script's internal state, so potential callables invoked in properties work. + { + MutexLock lock(ManagedCallable::instances_mutex); + + for (const KeyValue<ManagedCallable *, Array> &elem : ManagedCallable::instances_pending_reload) { + ManagedCallable *managed_callable = elem.key; + const Array &serialized_data = elem.value; + + GCHandleIntPtr delegate = { nullptr }; + + bool success = GDMonoCache::managed_callbacks.DelegateUtils_TryDeserializeDelegateWithGCHandle( + &serialized_data, &delegate); + + if (success) { + ERR_CONTINUE(delegate.value == nullptr); + managed_callable->delegate_handle = delegate; + } else if (OS::get_singleton()->is_stdout_verbose()) { + OS::get_singleton()->print("Failed to deserialize delegate\n"); + } + } + + ManagedCallable::instances_pending_reload.clear(); + } + for (Ref<CSharpScript> &scr : to_reload_state) { for (const ObjectID &obj_id : scr->pending_reload_instances) { Object *obj = ObjectDB::get_instance(obj_id); @@ -963,7 +988,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { properties[G.first] = G.second; } - // Restore serialized state and call OnAfterDeserialization + // Restore serialized state and call OnAfterDeserialize. GDMonoCache::managed_callbacks.CSharpInstanceBridge_DeserializeState( csi->get_gchandle_intptr(), &properties, &state_backup.event_signals); } @@ -973,30 +998,6 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { scr->pending_reload_state.clear(); } - // Deserialize managed callables - { - MutexLock lock(ManagedCallable::instances_mutex); - - for (const KeyValue<ManagedCallable *, Array> &elem : ManagedCallable::instances_pending_reload) { - ManagedCallable *managed_callable = elem.key; - const Array &serialized_data = elem.value; - - GCHandleIntPtr delegate = { nullptr }; - - bool success = GDMonoCache::managed_callbacks.DelegateUtils_TryDeserializeDelegateWithGCHandle( - &serialized_data, &delegate); - - if (success) { - ERR_CONTINUE(delegate.value == nullptr); - managed_callable->delegate_handle = delegate; - } else if (OS::get_singleton()->is_stdout_verbose()) { - OS::get_singleton()->print("Failed to deserialize delegate\n"); - } - } - - ManagedCallable::instances_pending_reload.clear(); - } - #ifdef TOOLS_ENABLED // FIXME: Hack to refresh editor in order to display new properties and signals. See if there is a better alternative. if (Engine::get_singleton()->is_editor_hint()) { diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index eb45ade285..e1ce41edd5 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -2581,6 +2581,10 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte p_output.append("\")]"); } + if (p_iprop.is_hidden) { + p_output.append(MEMBER_BEGIN "[EditorBrowsable(EditorBrowsableState.Never)]"); + } + p_output.append(MEMBER_BEGIN "public "); if (prop_allowed_inherited_member_hiding.has(p_itype.proxy_name + "." + p_iprop.proxy_name)) { @@ -2840,7 +2844,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output.append("\")]"); } - if (p_imethod.is_compat) { + if (p_imethod.is_hidden) { p_output.append(MEMBER_BEGIN "[EditorBrowsable(EditorBrowsableState.Never)]"); } @@ -3654,11 +3658,16 @@ bool BindingsGenerator::_populate_object_type_interfaces() { iprop.setter = ClassDB::get_property_setter(type_cname, iprop.cname); iprop.getter = ClassDB::get_property_getter(type_cname, iprop.cname); - if (iprop.setter != StringName()) { - accessor_methods[iprop.setter] = iprop.cname; - } - if (iprop.getter != StringName()) { - accessor_methods[iprop.getter] = iprop.cname; + // If the property is internal hide it; otherwise, hide the getter and setter. + if (property.usage & PROPERTY_USAGE_INTERNAL) { + iprop.is_hidden = true; + } else { + if (iprop.setter != StringName()) { + accessor_methods[iprop.setter] = iprop.cname; + } + if (iprop.getter != StringName()) { + accessor_methods[iprop.getter] = iprop.cname; + } } bool valid = false; @@ -3860,10 +3869,10 @@ bool BindingsGenerator::_populate_object_type_interfaces() { HashMap<StringName, StringName>::Iterator accessor = accessor_methods.find(imethod.cname); if (accessor) { - // We only make internal an accessor method if it's in the same class as the property. + // We only hide an accessor method if it's in the same class as the property. // It's easier this way, but also we don't know if an accessor method in a different class // could have other purposes, so better leave those untouched. - imethod.is_internal = true; + imethod.is_hidden = true; } if (itype.class_doc) { @@ -3892,6 +3901,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { // after all the non-compat methods have been added. The compat methods are added in // reverse so the most recently added ones take precedence over older compat methods. if (imethod.is_compat) { + imethod.is_hidden = true; compat_methods.push_front(imethod); continue; } diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index bb0ba0cb00..a397dcb026 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -88,6 +88,14 @@ class BindingsGenerator { StringName setter; StringName getter; + /** + * Determines if the property will be hidden with the [EditorBrowsable(EditorBrowsableState.Never)] + * attribute. + * We do this for propertyies that have the PROPERTY_USAGE_INTERNAL flag, because they are not meant + * to be exposed to scripting but we can't remove them to prevent breaking compatibility. + */ + bool is_hidden = false; + const DocData::PropertyDoc *prop_doc; bool is_deprecated = false; @@ -180,6 +188,14 @@ class BindingsGenerator { bool is_internal = false; /** + * Determines if the method will be hidden with the [EditorBrowsable(EditorBrowsableState.Never)] + * attribute. + * We do this for methods that we don't want to expose but need to be public to prevent breaking + * compat (i.e: methods with 'is_compat' set to true.) + */ + bool is_hidden = false; + + /** * Determines if the method is a compatibility method added to avoid breaking binary compatibility. * These methods will be generated but hidden and are considered deprecated. */ diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index 9cd5498fa8..37f319b697 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -1208,39 +1208,39 @@ namespace Godot /// Do a simple expression match, where '*' matches zero or more /// arbitrary characters and '?' matches any single character except '.'. /// </summary> - /// <param name="instance">The string to check.</param> - /// <param name="expr">Expression to check.</param> + /// <param name="str">The string to check.</param> + /// <param name="pattern">Expression to check.</param> /// <param name="caseSensitive"> /// If <see langword="true"/>, the check will be case sensitive. /// </param> /// <returns>If the expression has any matches.</returns> - private static bool ExprMatch(this string instance, string expr, bool caseSensitive) + private static bool WildcardMatch(ReadOnlySpan<char> str, ReadOnlySpan<char> pattern, bool caseSensitive) { // case '\0': - if (expr.Length == 0) - return instance.Length == 0; + if (pattern.IsEmpty) + return str.IsEmpty; - switch (expr[0]) + switch (pattern[0]) { case '*': - return ExprMatch(instance, expr.Substring(1), caseSensitive) || (instance.Length > 0 && - ExprMatch(instance.Substring(1), expr, caseSensitive)); + return WildcardMatch(str, pattern.Slice(1), caseSensitive) + || (!str.IsEmpty && WildcardMatch(str.Slice(1), pattern, caseSensitive)); case '?': - return instance.Length > 0 && instance[0] != '.' && - ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive); + return !str.IsEmpty && str[0] != '.' && + WildcardMatch(str.Slice(1), pattern.Slice(1), caseSensitive); default: - if (instance.Length == 0) + if (str.IsEmpty) return false; - if (caseSensitive) - return instance[0] == expr[0]; - return (char.ToUpperInvariant(instance[0]) == char.ToUpperInvariant(expr[0])) && - ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive); + bool charMatches = caseSensitive ? + str[0] == pattern[0] : + char.ToUpperInvariant(str[0]) == char.ToUpperInvariant(pattern[0]); + return charMatches && + WildcardMatch(str.Slice(1), pattern.Slice(1), caseSensitive); } } /// <summary> - /// Do a simple case sensitive expression match, using ? and * wildcards - /// (see <see cref="ExprMatch(string, string, bool)"/>). + /// Do a simple case sensitive expression match, using ? and * wildcards. /// </summary> /// <seealso cref="MatchN(string, string)"/> /// <param name="instance">The string to check.</param> @@ -1254,12 +1254,11 @@ namespace Godot if (instance.Length == 0 || expr.Length == 0) return false; - return instance.ExprMatch(expr, caseSensitive); + return WildcardMatch(instance, expr, caseSensitive); } /// <summary> - /// Do a simple case insensitive expression match, using ? and * wildcards - /// (see <see cref="ExprMatch(string, string, bool)"/>). + /// Do a simple case insensitive expression match, using ? and * wildcards. /// </summary> /// <seealso cref="Match(string, string, bool)"/> /// <param name="instance">The string to check.</param> @@ -1270,7 +1269,7 @@ namespace Godot if (instance.Length == 0 || expr.Length == 0) return false; - return instance.ExprMatch(expr, caseSensitive: false); + return WildcardMatch(instance, expr, caseSensitive: false); } /// <summary> diff --git a/modules/navigation/2d/godot_navigation_server_2d.cpp b/modules/navigation/2d/godot_navigation_server_2d.cpp index 5eefbe4228..bf69adc14c 100644 --- a/modules/navigation/2d/godot_navigation_server_2d.cpp +++ b/modules/navigation/2d/godot_navigation_server_2d.cpp @@ -389,7 +389,16 @@ bool FORWARD_1_C(agent_is_map_changed, RID, p_agent, rid_to_rid); void FORWARD_2(agent_set_paused, RID, p_agent, bool, p_paused, rid_to_rid, bool_to_bool); bool FORWARD_1_C(agent_get_paused, RID, p_agent, rid_to_rid); -void FORWARD_1(free, RID, p_object, rid_to_rid); +void GodotNavigationServer2D::free(RID p_object) { +#ifdef CLIPPER2_ENABLED + if (navmesh_generator_2d && navmesh_generator_2d->owns(p_object)) { + navmesh_generator_2d->free(p_object); + return; + } +#endif // CLIPPER2_ENABLED + NavigationServer3D::get_singleton()->free(p_object); +} + void FORWARD_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback, rid_to_rid, callable_to_callable); bool GodotNavigationServer2D::agent_has_avoidance_callback(RID p_agent) const { return NavigationServer3D::get_singleton()->agent_has_avoidance_callback(p_agent); @@ -453,3 +462,20 @@ void GodotNavigationServer2D::query_path(const Ref<NavigationPathQueryParameters p_query_result->set_path_rids(_query_result.path_rids); p_query_result->set_path_owner_ids(_query_result.path_owner_ids); } + +RID GodotNavigationServer2D::source_geometry_parser_create() { +#ifdef CLIPPER2_ENABLED + if (navmesh_generator_2d) { + return navmesh_generator_2d->source_geometry_parser_create(); + } +#endif // CLIPPER2_ENABLED + return RID(); +} + +void GodotNavigationServer2D::source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) { +#ifdef CLIPPER2_ENABLED + if (navmesh_generator_2d) { + navmesh_generator_2d->source_geometry_parser_set_callback(p_parser, p_callback); + } +#endif // CLIPPER2_ENABLED +} diff --git a/modules/navigation/2d/godot_navigation_server_2d.h b/modules/navigation/2d/godot_navigation_server_2d.h index ba375afd33..ea77fa5e6e 100644 --- a/modules/navigation/2d/godot_navigation_server_2d.h +++ b/modules/navigation/2d/godot_navigation_server_2d.h @@ -253,6 +253,9 @@ public: virtual void bake_from_source_geometry_data_async(const Ref<NavigationPolygon> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData2D> &p_source_geometry_data, const Callable &p_callback = Callable()) override; virtual bool is_baking_navigation_polygon(Ref<NavigationPolygon> p_navigation_polygon) const override; + virtual RID source_geometry_parser_create() override; + virtual void source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) override; + virtual Vector<Vector2> simplify_path(const Vector<Vector2> &p_path, real_t p_epsilon) override; }; diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp index d8f1170f6a..13399b858e 100644 --- a/modules/navigation/2d/nav_mesh_generator_2d.cpp +++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp @@ -43,9 +43,9 @@ #include "scene/resources/2d/circle_shape_2d.h" #include "scene/resources/2d/concave_polygon_shape_2d.h" #include "scene/resources/2d/convex_polygon_shape_2d.h" +#include "scene/resources/2d/navigation_mesh_source_geometry_data_2d.h" +#include "scene/resources/2d/navigation_polygon.h" #include "scene/resources/2d/rectangle_shape_2d.h" -#include "scene/resources/navigation_mesh_source_geometry_data_2d.h" -#include "scene/resources/navigation_polygon.h" #include "thirdparty/clipper2/include/clipper2/clipper.h" #include "thirdparty/misc/polypartition.h" @@ -53,11 +53,14 @@ NavMeshGenerator2D *NavMeshGenerator2D::singleton = nullptr; Mutex NavMeshGenerator2D::baking_navmesh_mutex; Mutex NavMeshGenerator2D::generator_task_mutex; +RWLock NavMeshGenerator2D::generator_rid_rwlock; bool NavMeshGenerator2D::use_threads = true; bool NavMeshGenerator2D::baking_use_multiple_threads = true; bool NavMeshGenerator2D::baking_use_high_priority_threads = true; HashSet<Ref<NavigationPolygon>> NavMeshGenerator2D::baking_navmeshes; HashMap<WorkerThreadPool::TaskID, NavMeshGenerator2D::NavMeshGeneratorTask2D *> NavMeshGenerator2D::generator_tasks; +RID_Owner<NavMeshGenerator2D::NavMeshGeometryParser2D> NavMeshGenerator2D::generator_parser_owner; +LocalVector<NavMeshGenerator2D::NavMeshGeometryParser2D *> NavMeshGenerator2D::generator_parsers; NavMeshGenerator2D *NavMeshGenerator2D::get_singleton() { return singleton; @@ -126,6 +129,13 @@ void NavMeshGenerator2D::cleanup() { } generator_tasks.clear(); + generator_rid_rwlock.write_lock(); + for (NavMeshGeometryParser2D *parser : generator_parsers) { + generator_parser_owner.free(parser->self); + } + generator_parsers.clear(); + generator_rid_rwlock.write_unlock(); + generator_task_mutex.unlock(); baking_navmesh_mutex.unlock(); } @@ -236,6 +246,15 @@ void NavMeshGenerator2D::generator_parse_geometry_node(Ref<NavigationPolygon> p_ generator_parse_tilemap_node(p_navigation_mesh, p_source_geometry_data, p_node); generator_parse_navigationobstacle_node(p_navigation_mesh, p_source_geometry_data, p_node); + generator_rid_rwlock.read_lock(); + for (const NavMeshGeometryParser2D *parser : generator_parsers) { + if (!parser->callback.is_valid()) { + continue; + } + parser->callback.call(p_navigation_mesh, p_source_geometry_data, p_node); + } + generator_rid_rwlock.read_unlock(); + if (p_recurse_children) { for (int i = 0; i < p_node->get_child_count(); i++) { generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, p_node->get_child(i), p_recurse_children); @@ -813,6 +832,47 @@ bool NavMeshGenerator2D::generator_emit_callback(const Callable &p_callback) { return ce.error == Callable::CallError::CALL_OK; } +RID NavMeshGenerator2D::source_geometry_parser_create() { + RWLockWrite write_lock(generator_rid_rwlock); + + RID rid = generator_parser_owner.make_rid(); + + NavMeshGeometryParser2D *parser = generator_parser_owner.get_or_null(rid); + parser->self = rid; + + generator_parsers.push_back(parser); + + return rid; +} + +void NavMeshGenerator2D::source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) { + RWLockWrite write_lock(generator_rid_rwlock); + + NavMeshGeometryParser2D *parser = generator_parser_owner.get_or_null(p_parser); + ERR_FAIL_NULL(parser); + + parser->callback = p_callback; +} + +bool NavMeshGenerator2D::owns(RID p_object) { + RWLockRead read_lock(generator_rid_rwlock); + return generator_parser_owner.owns(p_object); +} + +void NavMeshGenerator2D::free(RID p_object) { + RWLockWrite write_lock(generator_rid_rwlock); + + if (generator_parser_owner.owns(p_object)) { + NavMeshGeometryParser2D *parser = generator_parser_owner.get_or_null(p_object); + + generator_parsers.erase(parser); + + generator_parser_owner.free(p_object); + } else { + ERR_PRINT("Attempted to free a NavMeshGenerator2D RID that did not exist (or was already freed)."); + } +} + void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data) { if (p_navigation_mesh.is_null() || p_source_geometry_data.is_null()) { return; diff --git a/modules/navigation/2d/nav_mesh_generator_2d.h b/modules/navigation/2d/nav_mesh_generator_2d.h index 2567a170ef..235a84d548 100644 --- a/modules/navigation/2d/nav_mesh_generator_2d.h +++ b/modules/navigation/2d/nav_mesh_generator_2d.h @@ -35,6 +35,7 @@ #include "core/object/class_db.h" #include "core/object/worker_thread_pool.h" +#include "core/templates/rid_owner.h" class Node; class NavigationPolygon; @@ -46,6 +47,14 @@ class NavMeshGenerator2D : public Object { static Mutex baking_navmesh_mutex; static Mutex generator_task_mutex; + static RWLock generator_rid_rwlock; + struct NavMeshGeometryParser2D { + RID self; + Callable callback; + }; + static RID_Owner<NavMeshGeometryParser2D> generator_parser_owner; + static LocalVector<NavMeshGeometryParser2D *> generator_parsers; + static bool use_threads; static bool baking_use_multiple_threads; static bool baking_use_high_priority_threads; @@ -97,6 +106,12 @@ public: static void bake_from_source_geometry_data_async(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, const Callable &p_callback = Callable()); static bool is_baking(Ref<NavigationPolygon> p_navigation_polygon); + static RID source_geometry_parser_create(); + static void source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback); + + static bool owns(RID p_object); + static void free(RID p_object); + NavMeshGenerator2D(); ~NavMeshGenerator2D(); }; diff --git a/modules/navigation/3d/godot_navigation_server_3d.cpp b/modules/navigation/3d/godot_navigation_server_3d.cpp index 301b4aa8f3..61a128e004 100644 --- a/modules/navigation/3d/godot_navigation_server_3d.cpp +++ b/modules/navigation/3d/godot_navigation_server_3d.cpp @@ -1202,6 +1202,11 @@ COMMAND_1(free, RID, p_object) { } else if (obstacle_owner.owns(p_object)) { internal_free_obstacle(p_object); +#ifndef _3D_DISABLED + } else if (navmesh_generator_3d && navmesh_generator_3d->owns(p_object)) { + navmesh_generator_3d->free(p_object); +#endif // _3D_DISABLED + } else { ERR_PRINT("Attempted to free a NavigationServer RID that did not exist (or was already freed)."); } @@ -1428,6 +1433,23 @@ PathQueryResult GodotNavigationServer3D::_query_path(const PathQueryParameters & return r_query_result; } +RID GodotNavigationServer3D::source_geometry_parser_create() { +#ifndef _3D_DISABLED + if (navmesh_generator_3d) { + return navmesh_generator_3d->source_geometry_parser_create(); + } +#endif // _3D_DISABLED + return RID(); +} + +void GodotNavigationServer3D::source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) { +#ifndef _3D_DISABLED + if (navmesh_generator_3d) { + navmesh_generator_3d->source_geometry_parser_set_callback(p_parser, p_callback); + } +#endif // _3D_DISABLED +} + Vector<Vector3> GodotNavigationServer3D::simplify_path(const Vector<Vector3> &p_path, real_t p_epsilon) { if (p_path.size() <= 2) { return p_path; diff --git a/modules/navigation/3d/godot_navigation_server_3d.h b/modules/navigation/3d/godot_navigation_server_3d.h index 89839ff459..5ba7ed1088 100644 --- a/modules/navigation/3d/godot_navigation_server_3d.h +++ b/modules/navigation/3d/godot_navigation_server_3d.h @@ -264,6 +264,9 @@ public: virtual void bake_from_source_geometry_data_async(const Ref<NavigationMesh> &p_navigation_mesh, const Ref<NavigationMeshSourceGeometryData3D> &p_source_geometry_data, const Callable &p_callback = Callable()) override; virtual bool is_baking_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh) const override; + virtual RID source_geometry_parser_create() override; + virtual void source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) override; + virtual Vector<Vector3> simplify_path(const Vector<Vector3> &p_path, real_t p_epsilon) override; private: diff --git a/modules/navigation/3d/nav_mesh_generator_3d.cpp b/modules/navigation/3d/nav_mesh_generator_3d.cpp index 3d3f4b4679..cc3bbdbf01 100644 --- a/modules/navigation/3d/nav_mesh_generator_3d.cpp +++ b/modules/navigation/3d/nav_mesh_generator_3d.cpp @@ -45,12 +45,12 @@ #include "scene/resources/3d/convex_polygon_shape_3d.h" #include "scene/resources/3d/cylinder_shape_3d.h" #include "scene/resources/3d/height_map_shape_3d.h" +#include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h" #include "scene/resources/3d/primitive_meshes.h" #include "scene/resources/3d/shape_3d.h" #include "scene/resources/3d/sphere_shape_3d.h" #include "scene/resources/3d/world_boundary_shape_3d.h" #include "scene/resources/navigation_mesh.h" -#include "scene/resources/navigation_mesh_source_geometry_data_3d.h" #include "modules/modules_enabled.gen.h" // For csg, gridmap. @@ -66,11 +66,14 @@ NavMeshGenerator3D *NavMeshGenerator3D::singleton = nullptr; Mutex NavMeshGenerator3D::baking_navmesh_mutex; Mutex NavMeshGenerator3D::generator_task_mutex; +RWLock NavMeshGenerator3D::generator_rid_rwlock; bool NavMeshGenerator3D::use_threads = true; bool NavMeshGenerator3D::baking_use_multiple_threads = true; bool NavMeshGenerator3D::baking_use_high_priority_threads = true; HashSet<Ref<NavigationMesh>> NavMeshGenerator3D::baking_navmeshes; HashMap<WorkerThreadPool::TaskID, NavMeshGenerator3D::NavMeshGeneratorTask3D *> NavMeshGenerator3D::generator_tasks; +RID_Owner<NavMeshGenerator3D::NavMeshGeometryParser3D> NavMeshGenerator3D::generator_parser_owner; +LocalVector<NavMeshGenerator3D::NavMeshGeometryParser3D *> NavMeshGenerator3D::generator_parsers; NavMeshGenerator3D *NavMeshGenerator3D::get_singleton() { return singleton; @@ -139,6 +142,13 @@ void NavMeshGenerator3D::cleanup() { } generator_tasks.clear(); + generator_rid_rwlock.write_lock(); + for (NavMeshGeometryParser3D *parser : generator_parsers) { + generator_parser_owner.free(parser->self); + } + generator_parsers.clear(); + generator_rid_rwlock.write_unlock(); + generator_task_mutex.unlock(); baking_navmesh_mutex.unlock(); } @@ -254,6 +264,15 @@ void NavMeshGenerator3D::generator_parse_geometry_node(const Ref<NavigationMesh> #endif generator_parse_navigationobstacle_node(p_navigation_mesh, p_source_geometry_data, p_node); + generator_rid_rwlock.read_lock(); + for (const NavMeshGeometryParser3D *parser : generator_parsers) { + if (!parser->callback.is_valid()) { + continue; + } + parser->callback.call(p_navigation_mesh, p_source_geometry_data, p_node); + } + generator_rid_rwlock.read_unlock(); + if (p_recurse_children) { for (int i = 0; i < p_node->get_child_count(); i++) { generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, p_node->get_child(i), p_recurse_children); @@ -920,4 +939,45 @@ bool NavMeshGenerator3D::generator_emit_callback(const Callable &p_callback) { return ce.error == Callable::CallError::CALL_OK; } +RID NavMeshGenerator3D::source_geometry_parser_create() { + RWLockWrite write_lock(generator_rid_rwlock); + + RID rid = generator_parser_owner.make_rid(); + + NavMeshGeometryParser3D *parser = generator_parser_owner.get_or_null(rid); + parser->self = rid; + + generator_parsers.push_back(parser); + + return rid; +} + +void NavMeshGenerator3D::source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback) { + RWLockWrite write_lock(generator_rid_rwlock); + + NavMeshGeometryParser3D *parser = generator_parser_owner.get_or_null(p_parser); + ERR_FAIL_NULL(parser); + + parser->callback = p_callback; +} + +bool NavMeshGenerator3D::owns(RID p_object) { + RWLockRead read_lock(generator_rid_rwlock); + return generator_parser_owner.owns(p_object); +} + +void NavMeshGenerator3D::free(RID p_object) { + RWLockWrite write_lock(generator_rid_rwlock); + + if (generator_parser_owner.owns(p_object)) { + NavMeshGeometryParser3D *parser = generator_parser_owner.get_or_null(p_object); + + generator_parsers.erase(parser); + + generator_parser_owner.free(p_object); + } else { + ERR_PRINT("Attempted to free a NavMeshGenerator3D RID that did not exist (or was already freed)."); + } +} + #endif // _3D_DISABLED diff --git a/modules/navigation/3d/nav_mesh_generator_3d.h b/modules/navigation/3d/nav_mesh_generator_3d.h index 9c9b3bdefe..b46a1736e0 100644 --- a/modules/navigation/3d/nav_mesh_generator_3d.h +++ b/modules/navigation/3d/nav_mesh_generator_3d.h @@ -35,6 +35,7 @@ #include "core/object/class_db.h" #include "core/object/worker_thread_pool.h" +#include "core/templates/rid_owner.h" #include "modules/modules_enabled.gen.h" // For csg, gridmap. class Node; @@ -47,6 +48,14 @@ class NavMeshGenerator3D : public Object { static Mutex baking_navmesh_mutex; static Mutex generator_task_mutex; + static RWLock generator_rid_rwlock; + struct NavMeshGeometryParser3D { + RID self; + Callable callback; + }; + static RID_Owner<NavMeshGeometryParser3D> generator_parser_owner; + static LocalVector<NavMeshGeometryParser3D *> generator_parsers; + static bool use_threads; static bool baking_use_multiple_threads; static bool baking_use_high_priority_threads; @@ -102,6 +111,12 @@ public: static void bake_from_source_geometry_data_async(Ref<NavigationMesh> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, const Callable &p_callback = Callable()); static bool is_baking(Ref<NavigationMesh> p_navigation_mesh); + static RID source_geometry_parser_create(); + static void source_geometry_parser_set_callback(RID p_parser, const Callable &p_callback); + + static bool owns(RID p_object); + static void free(RID p_object); + NavMeshGenerator3D(); ~NavMeshGenerator3D(); }; diff --git a/modules/navigation/3d/navigation_mesh_generator.cpp b/modules/navigation/3d/navigation_mesh_generator.cpp index 8393896db1..54df42e266 100644 --- a/modules/navigation/3d/navigation_mesh_generator.cpp +++ b/modules/navigation/3d/navigation_mesh_generator.cpp @@ -32,7 +32,7 @@ #include "navigation_mesh_generator.h" -#include "scene/resources/navigation_mesh_source_geometry_data_3d.h" +#include "scene/resources/3d/navigation_mesh_source_geometry_data_3d.h" #include "servers/navigation_server_3d.h" NavigationMeshGenerator *NavigationMeshGenerator::singleton = nullptr; diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp index 352203f5aa..d7bf1cdd38 100644 --- a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp +++ b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp @@ -42,7 +42,6 @@ #include "scene/gui/button.h" #include "scene/gui/dialogs.h" #include "scene/gui/label.h" -#include "scene/resources/navigation_mesh_source_geometry_data_3d.h" void NavigationMeshEditor::_node_removed(Node *p_node) { if (p_node == node) { diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml index 168e0bf077..b9c69075e1 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OpenXRCompositionLayer" inherits="Node3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="OpenXRCompositionLayer" inherits="Node3D" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> The parent class of all OpenXR composition layer nodes. </brief_description> diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml b/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml index 2de1977671..dd8a11e2b9 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OpenXRCompositionLayerCylinder" inherits="OpenXRCompositionLayer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="OpenXRCompositionLayerCylinder" inherits="OpenXRCompositionLayer" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> An OpenXR composition layer that is rendered as an internal slice of a cylinder. </brief_description> diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml b/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml index f6eba7e228..716ea72854 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OpenXRCompositionLayerEquirect" inherits="OpenXRCompositionLayer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="OpenXRCompositionLayerEquirect" inherits="OpenXRCompositionLayer" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> An OpenXR composition layer that is rendered as an internal slice of a sphere. </brief_description> diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml b/modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml index fff592bc4f..6632f90ed2 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OpenXRCompositionLayerQuad" inherits="OpenXRCompositionLayer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="OpenXRCompositionLayerQuad" inherits="OpenXRCompositionLayer" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> An OpenXR composition layer that is rendered as a quad. </brief_description> diff --git a/modules/openxr/doc_classes/OpenXRHand.xml b/modules/openxr/doc_classes/OpenXRHand.xml index 9cc548dd6f..23d932ddd7 100644 --- a/modules/openxr/doc_classes/OpenXRHand.xml +++ b/modules/openxr/doc_classes/OpenXRHand.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OpenXRHand" inherits="SkeletonModifier3D" deprecated="Use [XRHandModifier3D] instead." xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="OpenXRHand" inherits="Node3D" deprecated="Use [XRHandModifier3D] instead." xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Node supporting hand and finger tracking in OpenXR. </brief_description> @@ -18,11 +18,14 @@ <member name="hand" type="int" setter="set_hand" getter="get_hand" enum="OpenXRHand.Hands" default="0"> Specifies whether this node tracks the left or right hand of the player. </member> + <member name="hand_skeleton" type="NodePath" setter="set_hand_skeleton" getter="get_hand_skeleton" default="NodePath("")"> + Set a [Skeleton3D] node for which the pose positions will be updated. + </member> <member name="motion_range" type="int" setter="set_motion_range" getter="get_motion_range" enum="OpenXRHand.MotionRange" default="0"> Set the motion range (if supported) limiting the hand motion. </member> <member name="skeleton_rig" type="int" setter="set_skeleton_rig" getter="get_skeleton_rig" enum="OpenXRHand.SkeletonRig" default="0"> - Set the type of skeleton rig the parent [Skeleton3D] is compliant with. + Set the type of skeleton rig the [member hand_skeleton] is compliant with. </member> </members> <constants> diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml index 1136ac1b69..05dff7d6ae 100644 --- a/modules/openxr/doc_classes/OpenXRInterface.xml +++ b/modules/openxr/doc_classes/OpenXRInterface.xml @@ -23,7 +23,7 @@ Returns display refresh rates supported by the current HMD. Only returned if this feature is supported by the OpenXR runtime and after the interface has been initialized. </description> </method> - <method name="get_hand_joint_angular_velocity" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_angular_velocity] obtained from [method XRServer.get_hand_tracker] instead."> + <method name="get_hand_joint_angular_velocity" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_angular_velocity] obtained from [method XRServer.get_tracker] instead."> <return type="Vector3" /> <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" /> <param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" /> @@ -31,7 +31,7 @@ If handtracking is enabled, returns the angular velocity of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is relative to [XROrigin3D]! </description> </method> - <method name="get_hand_joint_flags" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_flags] obtained from [method XRServer.get_hand_tracker] instead."> + <method name="get_hand_joint_flags" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_flags] obtained from [method XRServer.get_tracker] instead."> <return type="int" enum="OpenXRInterface.HandJointFlags" is_bitfield="true" /> <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" /> <param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" /> @@ -39,7 +39,7 @@ If handtracking is enabled, returns flags that inform us of the validity of the tracking data. </description> </method> - <method name="get_hand_joint_linear_velocity" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_linear_velocity] obtained from [method XRServer.get_hand_tracker] instead."> + <method name="get_hand_joint_linear_velocity" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_linear_velocity] obtained from [method XRServer.get_tracker] instead."> <return type="Vector3" /> <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" /> <param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" /> @@ -47,7 +47,7 @@ If handtracking is enabled, returns the linear velocity of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is relative to [XROrigin3D] without worldscale applied! </description> </method> - <method name="get_hand_joint_position" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_transform] obtained from [method XRServer.get_hand_tracker] instead."> + <method name="get_hand_joint_position" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_transform] obtained from [method XRServer.get_tracker] instead."> <return type="Vector3" /> <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" /> <param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" /> @@ -55,7 +55,7 @@ If handtracking is enabled, returns the position of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is relative to [XROrigin3D] without worldscale applied! </description> </method> - <method name="get_hand_joint_radius" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_radius] obtained from [method XRServer.get_hand_tracker] instead."> + <method name="get_hand_joint_radius" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_radius] obtained from [method XRServer.get_tracker] instead."> <return type="float" /> <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" /> <param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" /> @@ -63,7 +63,7 @@ If handtracking is enabled, returns the radius of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. This is without worldscale applied! </description> </method> - <method name="get_hand_joint_rotation" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_transform] obtained from [method XRServer.get_hand_tracker] instead."> + <method name="get_hand_joint_rotation" qualifiers="const" deprecated="Use [method XRHandTracker.get_hand_joint_transform] obtained from [method XRServer.get_tracker] instead."> <return type="Quaternion" /> <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" /> <param index="1" name="joint" type="int" enum="OpenXRInterface.HandJoints" /> @@ -71,7 +71,7 @@ If handtracking is enabled, returns the rotation of a joint ([param joint]) of a hand ([param hand]) as provided by OpenXR. </description> </method> - <method name="get_hand_tracking_source" qualifiers="const" deprecated="Use [member XRHandTracker.hand_tracking_source] obtained from [method XRServer.get_hand_tracker] instead."> + <method name="get_hand_tracking_source" qualifiers="const" deprecated="Use [member XRHandTracker.hand_tracking_source] obtained from [method XRServer.get_tracker] instead."> <return type="int" enum="OpenXRInterface.HandTrackedSource" /> <param index="0" name="hand" type="int" enum="OpenXRInterface.Hand" /> <description> diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp index b3c20ef8b9..f8cc3d1d8c 100644 --- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp +++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp @@ -196,7 +196,8 @@ void OpenXRHandTrackingExtension::on_process() { Ref<XRHandTracker> godot_tracker; godot_tracker.instantiate(); godot_tracker->set_hand(i == 0 ? XRHandTracker::HAND_LEFT : XRHandTracker::HAND_RIGHT); - XRServer::get_singleton()->add_hand_tracker(i == 0 ? "/user/left" : "/user/right", godot_tracker); + godot_tracker->set_tracker_name(i == 0 ? "/user/hand_tracker/left" : "/user/hand_tracker/right"); + XRServer::get_singleton()->add_tracker(godot_tracker); hand_trackers[i].godot_tracker = godot_tracker; hand_trackers[i].is_initialized = true; @@ -229,8 +230,7 @@ void OpenXRHandTrackingExtension::on_process() { // For some reason an inactive controller isn't coming back as inactive but has coordinates either as NAN or very large const XrPosef &palm = hand_trackers[i].joint_locations[XR_HAND_JOINT_PALM_EXT].pose; - if ( - !hand_trackers[i].locations.isActive || isnan(palm.position.x) || palm.position.x < -1000000.00 || palm.position.x > 1000000.00) { + if (!hand_trackers[i].locations.isActive || isnan(palm.position.x) || palm.position.x < -1000000.00 || palm.position.x > 1000000.00) { hand_trackers[i].locations.isActive = false; // workaround, make sure its inactive } @@ -249,6 +249,8 @@ void OpenXRHandTrackingExtension::on_process() { const XrPosef &pose = location.pose; Transform3D transform; + Vector3 linear_velocity; + Vector3 angular_velocity; BitField<XRHandTracker::HandJointFlags> flags; if (location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) { @@ -269,27 +271,34 @@ void OpenXRHandTrackingExtension::on_process() { } if (location.locationFlags & XR_SPACE_VELOCITY_LINEAR_VALID_BIT) { flags.set_flag(XRHandTracker::HAND_JOINT_FLAG_LINEAR_VELOCITY_VALID); - godot_tracker->set_hand_joint_linear_velocity((XRHandTracker::HandJoint)joint, Vector3(velocity.linearVelocity.x, velocity.linearVelocity.y, velocity.linearVelocity.z)); + linear_velocity = Vector3(velocity.linearVelocity.x, velocity.linearVelocity.y, velocity.linearVelocity.z); + godot_tracker->set_hand_joint_linear_velocity((XRHandTracker::HandJoint)joint, linear_velocity); } if (location.locationFlags & XR_SPACE_VELOCITY_ANGULAR_VALID_BIT) { flags.set_flag(XRHandTracker::HAND_JOINT_FLAG_ANGULAR_VELOCITY_VALID); - godot_tracker->set_hand_joint_angular_velocity((XRHandTracker::HandJoint)joint, Vector3(velocity.angularVelocity.x, velocity.angularVelocity.y, velocity.angularVelocity.z)); + angular_velocity = Vector3(velocity.angularVelocity.x, velocity.angularVelocity.y, velocity.angularVelocity.z); + godot_tracker->set_hand_joint_angular_velocity((XRHandTracker::HandJoint)joint, angular_velocity); } godot_tracker->set_hand_joint_flags((XRHandTracker::HandJoint)joint, flags); godot_tracker->set_hand_joint_transform((XRHandTracker::HandJoint)joint, transform); godot_tracker->set_hand_joint_radius((XRHandTracker::HandJoint)joint, location.radius); - XRHandTracker::HandTrackingSource source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN; - if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) { - source = XRHandTracker::HAND_TRACKING_SOURCE_UNOBSTRUCTED; - } else if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) { - source = XRHandTracker::HAND_TRACKING_SOURCE_CONTROLLER; + if (joint == XR_HAND_JOINT_PALM_EXT) { + XRHandTracker::HandTrackingSource source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN; + if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) { + source = XRHandTracker::HAND_TRACKING_SOURCE_UNOBSTRUCTED; + } else if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) { + source = XRHandTracker::HAND_TRACKING_SOURCE_CONTROLLER; + } + + godot_tracker->set_hand_tracking_source(source); + godot_tracker->set_pose("default", transform, linear_velocity, angular_velocity); } - godot_tracker->set_hand_tracking_source(source); } } else { godot_tracker->set_has_tracking_data(false); + godot_tracker->invalidate_pose("default"); } } } @@ -311,7 +320,7 @@ void OpenXRHandTrackingExtension::cleanup_hand_tracking() { hand_trackers[i].is_initialized = false; hand_trackers[i].hand_tracker = XR_NULL_HANDLE; - XRServer::get_singleton()->remove_hand_tracker(i == 0 ? "/user/left" : "/user/right"); + XRServer::get_singleton()->remove_tracker(hand_trackers[i].godot_tracker); } } } diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index 7eb9a6ebe1..aa68441f03 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -35,6 +35,7 @@ #include "servers/rendering/rendering_server_globals.h" #include "extensions/openxr_eye_gaze_interaction.h" +#include "thirdparty/openxr/include/openxr/openxr.h" void OpenXRInterface::_bind_methods() { // lifecycle signals @@ -154,9 +155,14 @@ PackedStringArray OpenXRInterface::get_suggested_tracker_names() const { // These are hardcoded in OpenXR, note that they will only be available if added to our action map PackedStringArray arr = { - "left_hand", // /user/hand/left is mapped to our defaults - "right_hand", // /user/hand/right is mapped to our defaults - "/user/treadmill", + "head", // XRPositionalTracker for the users head (Mapped from OpenXR /user/head) + "left_hand", // XRControllerTracker for the users left hand (Mapped from OpenXR /user/hand/left) + "right_hand", // XRControllerTracker for the users right hand (Mapped from OpenXR /user/hand/right) + "/user/hand_tracker/left", // XRHandTracker for the users left hand + "/user/hand_tracker/right", // XRHandTracker for the users right hand + "/user/body_tracker", // XRBodyTracker for the users body + "/user/face_tracker", // XRFaceTracker for the users face + "/user/treadmill" }; for (OpenXRExtensionWrapper *wrapper : OpenXRAPI::get_singleton()->get_registered_extension_wrappers()) { @@ -430,34 +436,31 @@ OpenXRInterface::Tracker *OpenXRInterface::find_tracker(const String &p_tracker_ RID tracker_rid = openxr_api->tracker_create(p_tracker_name); ERR_FAIL_COND_V(tracker_rid.is_null(), nullptr); - // create our positional tracker - Ref<XRPositionalTracker> positional_tracker; - positional_tracker.instantiate(); + // Create our controller tracker. + Ref<XRControllerTracker> controller_tracker; + controller_tracker.instantiate(); // We have standardized some names to make things nicer to the user so lets recognize the toplevel paths related to these. if (p_tracker_name == "/user/hand/left") { - positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); - positional_tracker->set_tracker_name("left_hand"); - positional_tracker->set_tracker_desc("Left hand controller"); - positional_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_LEFT); + controller_tracker->set_tracker_name("left_hand"); + controller_tracker->set_tracker_desc("Left hand controller"); + controller_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_LEFT); } else if (p_tracker_name == "/user/hand/right") { - positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); - positional_tracker->set_tracker_name("right_hand"); - positional_tracker->set_tracker_desc("Right hand controller"); - positional_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_RIGHT); + controller_tracker->set_tracker_name("right_hand"); + controller_tracker->set_tracker_desc("Right hand controller"); + controller_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_RIGHT); } else { - positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER); - positional_tracker->set_tracker_name(p_tracker_name); - positional_tracker->set_tracker_desc(p_tracker_name); + controller_tracker->set_tracker_name(p_tracker_name); + controller_tracker->set_tracker_desc(p_tracker_name); } - positional_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE); - xr_server->add_tracker(positional_tracker); + controller_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE); + xr_server->add_tracker(controller_tracker); // create a new entry tracker = memnew(Tracker); tracker->tracker_name = p_tracker_name; tracker->tracker_rid = tracker_rid; - tracker->positional_tracker = positional_tracker; + tracker->controller_tracker = controller_tracker; tracker->interaction_profile = RID(); trackers.push_back(tracker); @@ -477,17 +480,17 @@ void OpenXRInterface::tracker_profile_changed(RID p_tracker, RID p_interaction_p if (p_interaction_profile.is_null()) { print_verbose("OpenXR: Interaction profile for " + tracker->tracker_name + " changed to " + INTERACTION_PROFILE_NONE); - tracker->positional_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE); + tracker->controller_tracker->set_tracker_profile(INTERACTION_PROFILE_NONE); } else { String name = openxr_api->interaction_profile_get_name(p_interaction_profile); print_verbose("OpenXR: Interaction profile for " + tracker->tracker_name + " changed to " + name); - tracker->positional_tracker->set_tracker_profile(name); + tracker->controller_tracker->set_tracker_profile(name); } } void OpenXRInterface::handle_tracker(Tracker *p_tracker) { ERR_FAIL_NULL(openxr_api); - ERR_FAIL_COND(p_tracker->positional_tracker.is_null()); + ERR_FAIL_COND(p_tracker->controller_tracker.is_null()); // Note, which actions are actually bound to inputs are handled by our interaction profiles however interaction // profiles are suggested bindings for controller types we know about. OpenXR runtimes can stray away from these @@ -506,15 +509,15 @@ void OpenXRInterface::handle_tracker(Tracker *p_tracker) { switch (action->action_type) { case OpenXRAction::OPENXR_ACTION_BOOL: { bool pressed = openxr_api->get_action_bool(action->action_rid, p_tracker->tracker_rid); - p_tracker->positional_tracker->set_input(action->action_name, Variant(pressed)); + p_tracker->controller_tracker->set_input(action->action_name, Variant(pressed)); } break; case OpenXRAction::OPENXR_ACTION_FLOAT: { real_t value = openxr_api->get_action_float(action->action_rid, p_tracker->tracker_rid); - p_tracker->positional_tracker->set_input(action->action_name, Variant(value)); + p_tracker->controller_tracker->set_input(action->action_name, Variant(value)); } break; case OpenXRAction::OPENXR_ACTION_VECTOR2: { Vector2 value = openxr_api->get_action_vector2(action->action_rid, p_tracker->tracker_rid); - p_tracker->positional_tracker->set_input(action->action_name, Variant(value)); + p_tracker->controller_tracker->set_input(action->action_name, Variant(value)); } break; case OpenXRAction::OPENXR_ACTION_POSE: { Transform3D transform; @@ -523,9 +526,9 @@ void OpenXRInterface::handle_tracker(Tracker *p_tracker) { XRPose::TrackingConfidence confidence = openxr_api->get_action_pose(action->action_rid, p_tracker->tracker_rid, transform, linear, angular); if (confidence != XRPose::XR_TRACKING_CONFIDENCE_NONE) { - p_tracker->positional_tracker->set_pose(action->action_name, transform, linear, angular, confidence); + p_tracker->controller_tracker->set_pose(action->action_name, transform, linear, angular, confidence); } else { - p_tracker->positional_tracker->invalidate_pose(action->action_name); + p_tracker->controller_tracker->invalidate_pose(action->action_name); } } break; default: { @@ -567,8 +570,8 @@ void OpenXRInterface::free_trackers() { Tracker *tracker = trackers[i]; openxr_api->tracker_free(tracker->tracker_rid); - xr_server->remove_tracker(tracker->positional_tracker); - tracker->positional_tracker.unref(); + xr_server->remove_tracker(tracker->controller_tracker); + tracker->controller_tracker.unref(); memdelete(tracker); } @@ -1005,7 +1008,7 @@ void OpenXRInterface::handle_hand_tracking(const String &p_path, OpenXRHandTrack OpenXRHandTrackingExtension *hand_tracking_ext = OpenXRHandTrackingExtension::get_singleton(); if (hand_tracking_ext && hand_tracking_ext->get_active()) { OpenXRInterface::Tracker *tracker = find_tracker(p_path); - if (tracker && tracker->positional_tracker.is_valid()) { + if (tracker && tracker->controller_tracker.is_valid()) { XrSpaceLocationFlags location_flags = hand_tracking_ext->get_hand_joint_location_flags(p_hand, XR_HAND_JOINT_PALM_EXT); if (location_flags & (XR_SPACE_LOCATION_ORIENTATION_VALID_BIT + XR_SPACE_LOCATION_POSITION_VALID_BIT)) { @@ -1035,9 +1038,9 @@ void OpenXRInterface::handle_hand_tracking(const String &p_path, OpenXRHandTrack angular_velocity = hand_tracking_ext->get_hand_joint_angular_velocity(p_hand, XR_HAND_JOINT_PALM_EXT); } - tracker->positional_tracker->set_pose("skeleton", transform, linear_velocity, angular_velocity, confidence); + tracker->controller_tracker->set_pose("skeleton", transform, linear_velocity, angular_velocity, confidence); } else { - tracker->positional_tracker->invalidate_pose("skeleton"); + tracker->controller_tracker->invalidate_pose("skeleton"); } } } diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h index 737f22d642..e916c7dac2 100644 --- a/modules/openxr/openxr_interface.h +++ b/modules/openxr/openxr_interface.h @@ -35,8 +35,8 @@ #include "extensions/openxr_hand_tracking_extension.h" #include "openxr_api.h" +#include "servers/xr/xr_controller_tracker.h" #include "servers/xr/xr_interface.h" -#include "servers/xr/xr_positional_tracker.h" // declare some default strings #define INTERACTION_PROFILE_NONE "/interaction_profiles/none" @@ -73,7 +73,7 @@ private: struct Tracker { // A tracker we've registered with OpenXR String tracker_name; // Name of our tracker (can be altered from the action map) Vector<Action *> actions; // Actions related to this tracker - Ref<XRPositionalTracker> positional_tracker; // Our positional tracker object that holds our tracker state + Ref<XRControllerTracker> controller_tracker; // Our positional tracker object that holds our tracker state RID tracker_rid; // RID of the tracker registered with our OpenXR API RID interaction_profile; // RID of the interaction profile bound to this tracker (can be null) }; diff --git a/modules/openxr/scene/openxr_hand.cpp b/modules/openxr/scene/openxr_hand.cpp index f20d1f8e19..2a4104f6ee 100644 --- a/modules/openxr/scene/openxr_hand.cpp +++ b/modules/openxr/scene/openxr_hand.cpp @@ -40,6 +40,9 @@ void OpenXRHand::_bind_methods() { ClassDB::bind_method(D_METHOD("set_hand", "hand"), &OpenXRHand::set_hand); ClassDB::bind_method(D_METHOD("get_hand"), &OpenXRHand::get_hand); + ClassDB::bind_method(D_METHOD("set_hand_skeleton", "hand_skeleton"), &OpenXRHand::set_hand_skeleton); + ClassDB::bind_method(D_METHOD("get_hand_skeleton"), &OpenXRHand::get_hand_skeleton); + ClassDB::bind_method(D_METHOD("set_motion_range", "motion_range"), &OpenXRHand::set_motion_range); ClassDB::bind_method(D_METHOD("get_motion_range"), &OpenXRHand::get_motion_range); @@ -51,6 +54,7 @@ void OpenXRHand::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "hand", PROPERTY_HINT_ENUM, "Left,Right"), "set_hand", "get_hand"); ADD_PROPERTY(PropertyInfo(Variant::INT, "motion_range", PROPERTY_HINT_ENUM, "Unobstructed,Conform to controller"), "set_motion_range", "get_motion_range"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "hand_skeleton", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Skeleton3D"), "set_hand_skeleton", "get_hand_skeleton"); ADD_PROPERTY(PropertyInfo(Variant::INT, "skeleton_rig", PROPERTY_HINT_ENUM, "OpenXR,Humanoid"), "set_skeleton_rig", "get_skeleton_rig"); ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_update", PROPERTY_HINT_ENUM, "Full,Rotation Only"), "set_bone_update", "get_bone_update"); @@ -86,6 +90,12 @@ OpenXRHand::Hands OpenXRHand::get_hand() const { return hand; } +void OpenXRHand::set_hand_skeleton(const NodePath &p_hand_skeleton) { + hand_skeleton = p_hand_skeleton; + + // TODO if inside tree call _get_bones() +} + void OpenXRHand::set_motion_range(MotionRange p_motion_range) { ERR_FAIL_INDEX(p_motion_range, MOTION_RANGE_MAX); motion_range = p_motion_range; @@ -97,6 +107,10 @@ OpenXRHand::MotionRange OpenXRHand::get_motion_range() const { return motion_range; } +NodePath OpenXRHand::get_hand_skeleton() const { + return hand_skeleton; +} + void OpenXRHand::_set_motion_range() { if (!hand_tracking_ext) { return; @@ -138,6 +152,20 @@ OpenXRHand::BoneUpdate OpenXRHand::get_bone_update() const { return bone_update; } +Skeleton3D *OpenXRHand::get_skeleton() { + if (!has_node(hand_skeleton)) { + return nullptr; + } + + Node *node = get_node(hand_skeleton); + if (!node) { + return nullptr; + } + + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(node); + return skeleton; +} + void OpenXRHand::_get_joint_data() { // Table of bone names for different rig types. static const String bone_names[SKELETON_RIG_MAX][XR_HAND_JOINT_COUNT_EXT] = { @@ -262,7 +290,7 @@ void OpenXRHand::_get_joint_data() { } } -void OpenXRHand::_process_modification() { +void OpenXRHand::_update_skeleton() { if (openxr_api == nullptr || !openxr_api->is_initialized()) { return; } else if (hand_tracking_ext == nullptr || !hand_tracking_ext->get_active()) { @@ -367,14 +395,21 @@ void OpenXRHand::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { _get_joint_data(); + + set_process_internal(true); } break; case NOTIFICATION_EXIT_TREE: { + set_process_internal(false); + // reset for (int i = 0; i < XR_HAND_JOINT_COUNT_EXT; i++) { joints[i].bone = -1; joints[i].parent_joint = -1; } } break; + case NOTIFICATION_INTERNAL_PROCESS: { + _update_skeleton(); + } break; default: { } break; } diff --git a/modules/openxr/scene/openxr_hand.h b/modules/openxr/scene/openxr_hand.h index fc0a994f48..4c77e7277c 100644 --- a/modules/openxr/scene/openxr_hand.h +++ b/modules/openxr/scene/openxr_hand.h @@ -31,15 +31,16 @@ #ifndef OPENXR_HAND_H #define OPENXR_HAND_H -#include "scene/3d/skeleton_modifier_3d.h" +#include "scene/3d/node_3d.h" +#include "scene/3d/skeleton_3d.h" #include <openxr/openxr.h> class OpenXRAPI; class OpenXRHandTrackingExtension; -class OpenXRHand : public SkeletonModifier3D { - GDCLASS(OpenXRHand, SkeletonModifier3D); +class OpenXRHand : public Node3D { + GDCLASS(OpenXRHand, Node3D); public: enum Hands { // Deprecated, need to change this to OpenXRInterface::Hands. @@ -85,13 +86,13 @@ private: void _set_motion_range(); + Skeleton3D *get_skeleton(); void _get_joint_data(); + void _update_skeleton(); protected: static void _bind_methods(); - virtual void _process_modification() override; - public: OpenXRHand(); @@ -101,6 +102,9 @@ public: void set_motion_range(MotionRange p_motion_range); MotionRange get_motion_range() const; + void set_hand_skeleton(const NodePath &p_hand_skeleton); + NodePath get_hand_skeleton() const; + void set_skeleton_rig(SkeletonRig p_skeleton_rig); SkeletonRig get_skeleton_rig() const; diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml index caf7958f6b..9fd4511d2b 100644 --- a/modules/webxr/doc_classes/WebXRInterface.xml +++ b/modules/webxr/doc_classes/WebXRInterface.xml @@ -114,10 +114,10 @@ </description> </method> <method name="get_input_source_tracker" qualifiers="const"> - <return type="XRPositionalTracker" /> + <return type="XRControllerTracker" /> <param index="0" name="input_source_id" type="int" /> <description> - Gets an [XRPositionalTracker] for the given [param input_source_id]. + Gets an [XRControllerTracker] for the given [param input_source_id]. 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] diff --git a/modules/webxr/webxr_interface.compat.inc b/modules/webxr/webxr_interface.compat.inc new file mode 100644 index 0000000000..97a9d44ca9 --- /dev/null +++ b/modules/webxr/webxr_interface.compat.inc @@ -0,0 +1,41 @@ +/**************************************************************************/ +/* webxr_interface.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +Ref<XRPositionalTracker> WebXRInterface::_get_input_source_tracker_bind_compat_90645(int p_input_source_id) const { + return get_input_source_tracker(p_input_source_id); +} + +void WebXRInterface::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("get_input_source_tracker", "input_source_id"), &WebXRInterface::_get_input_source_tracker_bind_compat_90645); +} + +#endif // DISABLE_DEPRECATED diff --git a/modules/webxr/webxr_interface.cpp b/modules/webxr/webxr_interface.cpp index c3efebef0f..4795fcdcd6 100644 --- a/modules/webxr/webxr_interface.cpp +++ b/modules/webxr/webxr_interface.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "webxr_interface.h" +#include "webxr_interface.compat.inc" #include <stdlib.h> diff --git a/modules/webxr/webxr_interface.h b/modules/webxr/webxr_interface.h index 06c18d0486..241dc9fe76 100644 --- a/modules/webxr/webxr_interface.h +++ b/modules/webxr/webxr_interface.h @@ -31,8 +31,8 @@ #ifndef WEBXR_INTERFACE_H #define WEBXR_INTERFACE_H +#include "servers/xr/xr_controller_tracker.h" #include "servers/xr/xr_interface.h" -#include "servers/xr/xr_positional_tracker.h" /** The WebXR interface is a VR/AR interface that can be used on the web. @@ -44,6 +44,11 @@ class WebXRInterface : public XRInterface { protected: static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + static void _bind_compatibility_methods(); + Ref<XRPositionalTracker> _get_input_source_tracker_bind_compat_90645(int p_input_source_id) const; +#endif + public: enum TargetRayMode { TARGET_RAY_MODE_UNKNOWN, @@ -64,7 +69,7 @@ public: virtual String get_reference_space_type() const = 0; virtual String get_enabled_features() 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 Ref<XRControllerTracker> 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 float get_display_refresh_rate() const = 0; diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp index c6213d1aae..535d464d6f 100644 --- a/modules/webxr/webxr_interface_js.cpp +++ b/modules/webxr/webxr_interface_js.cpp @@ -164,8 +164,8 @@ bool WebXRInterfaceJS::is_input_source_active(int p_input_source_id) const { return input_sources[p_input_source_id].active; } -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>()); +Ref<XRControllerTracker> WebXRInterfaceJS::get_input_source_tracker(int p_input_source_id) const { + ERR_FAIL_INDEX_V(p_input_source_id, input_source_count, Ref<XRControllerTracker>()); return input_sources[p_input_source_id].tracker; } @@ -307,7 +307,7 @@ void WebXRInterfaceJS::uninitialize() { for (int i = 0; i < HAND_MAX; i++) { if (hand_trackers[i].is_valid()) { - xr_server->remove_hand_tracker(i == 0 ? "/user/left" : "/user/right"); + xr_server->remove_tracker(hand_trackers[i]); hand_trackers[i].unref(); } @@ -616,7 +616,7 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) { input_source.target_ray_mode = (WebXRInterface::TargetRayMode)tmp_target_ray_mode; input_source.touch_index = touch_index; - Ref<XRPositionalTracker> &tracker = input_source.tracker; + Ref<XRControllerTracker> &tracker = input_source.tracker; if (tracker.is_null()) { tracker.instantiate(); @@ -630,7 +630,6 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) { // 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); @@ -715,6 +714,7 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) { if (unlikely(hand_tracker.is_null())) { hand_tracker.instantiate(); hand_tracker->set_hand(p_input_source_id == 0 ? XRHandTracker::HAND_LEFT : XRHandTracker::HAND_RIGHT); + hand_tracker->set_tracker_name(p_input_source_id == 0 ? "/user/hand_tracker/left" : "/user/hand_tracker/right"); // These flags always apply, since WebXR doesn't give us enough insight to be more fine grained. BitField<XRHandTracker::HandJointFlags> joint_flags(XRHandTracker::HAND_JOINT_FLAG_POSITION_VALID | XRHandTracker::HAND_JOINT_FLAG_ORIENTATION_VALID | XRHandTracker::HAND_JOINT_FLAG_POSITION_TRACKED | XRHandTracker::HAND_JOINT_FLAG_ORIENTATION_TRACKED); @@ -723,7 +723,7 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) { } hand_trackers[p_input_source_id] = hand_tracker; - xr_server->add_hand_tracker(p_input_source_id == 0 ? "/user/left" : "/user/right", hand_tracker); + xr_server->add_tracker(hand_tracker); } hand_tracker->set_has_tracking_data(true); @@ -746,10 +746,12 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) { Transform3D palm_transform; palm_transform.origin = (Vector3(start_pos[0], start_pos[1], start_pos[2]) + Vector3(end_pos[0], end_pos[1], end_pos[2])) / 2.0; hand_tracker->set_hand_joint_transform(XRHandTracker::HAND_JOINT_PALM, palm_transform); + hand_tracker->set_pose("default", palm_transform, Vector3(), Vector3()); } } else if (hand_tracker.is_valid()) { hand_tracker->set_has_tracking_data(false); + hand_tracker->invalidate_pose("default"); } } } diff --git a/modules/webxr/webxr_interface_js.h b/modules/webxr/webxr_interface_js.h index fc5df3a59b..afce28d410 100644 --- a/modules/webxr/webxr_interface_js.h +++ b/modules/webxr/webxr_interface_js.h @@ -33,6 +33,8 @@ #ifdef WEB_ENABLED +#include "servers/xr/xr_controller_tracker.h" +#include "servers/xr/xr_hand_tracker.h" #include "webxr_interface.h" /** @@ -68,7 +70,7 @@ private: static constexpr uint8_t input_source_count = 16; struct InputSource { - Ref<XRPositionalTracker> tracker; + Ref<XRControllerTracker> tracker; bool active = false; TargetRayMode target_ray_mode; int touch_index = -1; @@ -102,7 +104,7 @@ public: virtual String get_reference_space_type() const override; virtual String get_enabled_features() 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 Ref<XRControllerTracker> 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_play_area() const override; |