diff options
Diffstat (limited to 'modules')
30 files changed, 984 insertions, 317 deletions
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 7026d131e3..dd9e49fb8d 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -5326,8 +5326,21 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator return result; } -// TODO: Add safe/unsafe return variable (for variant cases) bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion, const GDScriptParser::Node *p_source_node) { +#ifdef DEBUG_ENABLED + if (p_source_node) { + if (p_target.kind == GDScriptParser::DataType::ENUM) { + if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) { + parser->push_warning(p_source_node, GDScriptWarning::INT_AS_ENUM_WITHOUT_CAST); + } + } + } +#endif + return check_type_compatibility(p_target, p_source, p_allow_implicit_conversion, p_source_node); +} + +// TODO: Add safe/unsafe return variable (for variant cases) +bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion, const GDScriptParser::Node *p_source_node) { // These return "true" so it doesn't affect users negatively. ERR_FAIL_COND_V_MSG(!p_target.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset target type"); ERR_FAIL_COND_V_MSG(!p_source.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset value type"); @@ -5362,11 +5375,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ if (p_target.kind == GDScriptParser::DataType::ENUM) { if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) { -#ifdef DEBUG_ENABLED - if (p_source_node) { - parser->push_warning(p_source_node, GDScriptWarning::INT_AS_ENUM_WITHOUT_CAST); - } -#endif return true; } if (p_source.kind == GDScriptParser::DataType::ENUM) { diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 4ed476a3df..e398ccfdbb 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -147,6 +147,7 @@ public: Variant make_variable_default_value(GDScriptParser::VariableNode *p_variable); const HashMap<String, Ref<GDScriptParserRef>> &get_depended_parsers(); + static bool check_type_compatibility(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr); GDScriptAnalyzer(GDScriptParser *p_parser); }; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index edba6340b9..13ed66710c 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1376,7 +1376,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code return GDScriptCodeGenerator::Address(); } - codegen.script->lambda_info.insert(function, { lambda->captures.size(), lambda->use_self }); + codegen.script->lambda_info.insert(function, { (int)lambda->captures.size(), lambda->use_self }); gen->write_lambda(result, function, captures, lambda->use_self); for (int i = 0; i < captures.size(); i++) { diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 210e2c3898..d921a73b9a 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -552,6 +552,19 @@ static int _get_property_location(const StringName &p_class, const StringName &p return depth | ScriptLanguage::LOCATION_PARENT_MASK; } +static int _get_property_location(Ref<Script> p_script, const StringName &p_property) { + int depth = 0; + Ref<Script> scr = p_script; + while (scr.is_valid()) { + if (scr->get_member_line(p_property) != -1) { + return depth | ScriptLanguage::LOCATION_PARENT_MASK; + } + depth++; + scr = scr->get_base_script(); + } + return depth + _get_property_location(p_script->get_instance_base_type(), p_property); +} + static int _get_constant_location(const StringName &p_class, const StringName &p_constant) { if (!ClassDB::has_integer_constant(p_class, p_constant)) { return ScriptLanguage::LOCATION_OTHER; @@ -567,6 +580,19 @@ static int _get_constant_location(const StringName &p_class, const StringName &p return depth | ScriptLanguage::LOCATION_PARENT_MASK; } +static int _get_constant_location(Ref<Script> p_script, const StringName &p_constant) { + int depth = 0; + Ref<Script> scr = p_script; + while (scr.is_valid()) { + if (scr->get_member_line(p_constant) != -1) { + return depth | ScriptLanguage::LOCATION_PARENT_MASK; + } + depth++; + scr = scr->get_base_script(); + } + return depth + _get_constant_location(p_script->get_instance_base_type(), p_constant); +} + static int _get_signal_location(const StringName &p_class, const StringName &p_signal) { if (!ClassDB::has_signal(p_class, p_signal)) { return ScriptLanguage::LOCATION_OTHER; @@ -582,6 +608,19 @@ static int _get_signal_location(const StringName &p_class, const StringName &p_s return depth | ScriptLanguage::LOCATION_PARENT_MASK; } +static int _get_signal_location(Ref<Script> p_script, const StringName &p_signal) { + int depth = 0; + Ref<Script> scr = p_script; + while (scr.is_valid()) { + if (scr->get_member_line(p_signal) != -1) { + return depth | ScriptLanguage::LOCATION_PARENT_MASK; + } + depth++; + scr = scr->get_base_script(); + } + return depth + _get_signal_location(p_script->get_instance_base_type(), p_signal); +} + static int _get_method_location(const StringName &p_class, const StringName &p_method) { if (!ClassDB::has_method(p_class, p_method)) { return ScriptLanguage::LOCATION_OTHER; @@ -597,6 +636,19 @@ static int _get_method_location(const StringName &p_class, const StringName &p_m return depth | ScriptLanguage::LOCATION_PARENT_MASK; } +static int _get_method_location(Ref<Script> p_script, const StringName &p_method) { + int depth = 0; + Ref<Script> scr = p_script; + while (scr.is_valid()) { + if (scr->get_member_line(p_method) != -1) { + return depth | ScriptLanguage::LOCATION_PARENT_MASK; + } + depth++; + scr = scr->get_base_script(); + } + return depth + _get_method_location(p_script->get_instance_base_type(), p_method); +} + static int _get_enum_constant_location(const StringName &p_class, const StringName &p_enum_constant) { if (!ClassDB::get_integer_constant_enum(p_class, p_enum_constant)) { return ScriptLanguage::LOCATION_OTHER; @@ -1089,7 +1141,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base if (E.name.contains("/")) { continue; } - int location = p_recursion_depth + _get_property_location(scr->get_class_name(), E.name); + int location = p_recursion_depth + _get_property_location(scr, E.name); ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); r_result.insert(option.display, option); } @@ -1097,7 +1149,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base List<MethodInfo> signals; scr->get_script_signal_list(&signals); for (const MethodInfo &E : signals) { - int location = p_recursion_depth + _get_signal_location(scr->get_class_name(), E.name); + int location = p_recursion_depth + _get_signal_location(scr, E.name); ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, location); r_result.insert(option.display, option); } @@ -1105,7 +1157,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base HashMap<StringName, Variant> constants; scr->get_constants(&constants); for (const KeyValue<StringName, Variant> &E : constants) { - int location = p_recursion_depth + _get_constant_location(scr->get_class_name(), E.key); + int location = p_recursion_depth + _get_constant_location(scr, E.key); ScriptLanguage::CodeCompletionOption option(E.key.operator String(), ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location); r_result.insert(option.display, option); } @@ -1117,7 +1169,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base if (E.name.begins_with("@")) { continue; } - int location = p_recursion_depth + _get_method_location(scr->get_class_name(), E.name); + int location = p_recursion_depth + _get_method_location(scr, E.name); ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location); if (E.arguments.size()) { option.insert_text += "("; @@ -2034,6 +2086,21 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, default: break; } + } else { + if (p_context.current_class) { + GDScriptCompletionIdentifier base_identifier; + + GDScriptCompletionIdentifier base; + base.value = p_context.base; + base.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + base.type.kind = GDScriptParser::DataType::CLASS; + base.type.class_type = p_context.current_class; + base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static; + + if (_guess_identifier_type_from_base(p_context, base, p_identifier->name, base_identifier)) { + id_type = base_identifier.type; + } + } } while (suite) { @@ -2087,8 +2154,15 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, if (last_assigned_expression && last_assign_line < p_context.current_line) { GDScriptParser::CompletionContext c = p_context; c.current_line = last_assign_line; - r_type.assigned_expression = last_assigned_expression; - if (_guess_expression_type(c, last_assigned_expression, r_type)) { + GDScriptCompletionIdentifier assigned_type; + if (_guess_expression_type(c, last_assigned_expression, assigned_type)) { + if (id_type.is_set() && assigned_type.type.is_set() && !GDScriptAnalyzer::check_type_compatibility(id_type, assigned_type.type)) { + // The assigned type is incompatible. The annotated type takes priority. + r_type.assigned_expression = last_assigned_expression; + r_type.type = id_type; + } else { + r_type = assigned_type; + } return true; } } @@ -2146,20 +2220,6 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, return true; } - // Check current class (including inheritance). - if (p_context.current_class) { - GDScriptCompletionIdentifier base; - base.value = p_context.base; - base.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - base.type.kind = GDScriptParser::DataType::CLASS; - base.type.class_type = p_context.current_class; - base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static; - - if (_guess_identifier_type_from_base(p_context, base, p_identifier->name, r_type)) { - return true; - } - } - // Check global scripts. if (ScriptServer::is_global_class(p_identifier->name)) { String script = ScriptServer::get_global_class_path(p_identifier->name); diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 7b03ac74d6..1a8c22cc11 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -655,7 +655,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } bool exit_ok = false; bool awaited = false; - int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? p_instance->members.size() : 0 }; + int variant_address_limits[ADDR_TYPE_MAX] = { _stack_size, _constant_count, p_instance ? (int)p_instance->members.size() : 0 }; #endif Variant *variant_addresses[ADDR_TYPE_MAX] = { stack, _constants_ptr, p_instance ? p_instance->members.ptrw() : nullptr }; diff --git a/modules/gltf/doc_classes/GLTFPhysicsBody.xml b/modules/gltf/doc_classes/GLTFPhysicsBody.xml index cf39721ce8..ca66cd54b0 100644 --- a/modules/gltf/doc_classes/GLTFPhysicsBody.xml +++ b/modules/gltf/doc_classes/GLTFPhysicsBody.xml @@ -4,7 +4,7 @@ Represents a GLTF physics body. </brief_description> <description> - Represents a physics body as defined by the [code]OMI_physics_body[/code] GLTF extension. This class is an intermediary between the GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future. + Represents a physics body as an intermediary between the [code]OMI_physics_body[/code] GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> @@ -15,7 +15,7 @@ <return type="GLTFPhysicsBody" /> <param index="0" name="dictionary" type="Dictionary" /> <description> - Creates a new GLTFPhysicsBody instance by parsing the given [Dictionary]. + Creates a new GLTFPhysicsBody instance by parsing the given [Dictionary] in the [code]OMI_physics_body[/code] GLTF extension format. </description> </method> <method name="from_node" qualifiers="static"> @@ -28,7 +28,7 @@ <method name="to_dictionary" qualifiers="const"> <return type="Dictionary" /> <description> - Serializes this GLTFPhysicsBody instance into a [Dictionary]. + Serializes this GLTFPhysicsBody instance into a [Dictionary]. It will be in the format expected by the [code]OMI_physics_body[/code] GLTF extension. </description> </method> <method name="to_node" qualifiers="const"> @@ -42,13 +42,20 @@ <member name="angular_velocity" type="Vector3" setter="set_angular_velocity" getter="get_angular_velocity" default="Vector3(0, 0, 0)"> The angular velocity of the physics body, in radians per second. This is only used when the body type is "rigid" or "vehicle". </member> - <member name="body_type" type="String" setter="set_body_type" getter="get_body_type" default=""static""> - The type of the body. When importing, this controls what type of [CollisionObject3D] node Godot should generate. Valid values are "static", "kinematic", "character", "rigid", "vehicle", and "trigger". + <member name="body_type" type="String" setter="set_body_type" getter="get_body_type" default=""rigid""> + The type of the body. When importing, this controls what type of [CollisionObject3D] node Godot should generate. Valid values are "static", "animatable", "character", "rigid", "vehicle", and "trigger". When exporting, this will be squashed down to one of "static", "kinematic", or "dynamic" motion types, or the "trigger" property. </member> <member name="center_of_mass" type="Vector3" setter="set_center_of_mass" getter="get_center_of_mass" default="Vector3(0, 0, 0)"> The center of mass of the body, in meters. This is in local space relative to the body. By default, the center of the mass is the body's origin. </member> - <member name="inertia_tensor" type="Basis" setter="set_inertia_tensor" getter="get_inertia_tensor" default="Basis(0, 0, 0, 0, 0, 0, 0, 0, 0)"> + <member name="inertia_diagonal" type="Vector3" setter="set_inertia_diagonal" getter="get_inertia_diagonal" default="Vector3(0, 0, 0)"> + The inertia strength of the physics body, in kilogram meter squared (kg⋅m²). This represents the inertia around the principle axes, the diagonal of the inertia tensor matrix. This is only used when the body type is "rigid" or "vehicle". + When converted to a Godot [RigidBody3D] node, if this value is zero, then the inertia will be calculated automatically. + </member> + <member name="inertia_orientation" type="Quaternion" setter="set_inertia_orientation" getter="get_inertia_orientation" default="Quaternion(0, 0, 0, 1)"> + The inertia orientation of the physics body. This defines the rotation of the inertia's principle axes relative to the object's local axes. This is only used when the body type is "rigid" or "vehicle" and [member inertia_diagonal] is set to a non-zero value. + </member> + <member name="inertia_tensor" type="Basis" setter="set_inertia_tensor" getter="get_inertia_tensor" default="Basis(0, 0, 0, 0, 0, 0, 0, 0, 0)" is_deprecated="true"> The inertia tensor of the physics body, in kilogram meter squared (kg⋅m²). This is only used when the body type is "rigid" or "vehicle". When converted to a Godot [RigidBody3D] node, if this value is zero, then the inertia will be calculated automatically. </member> diff --git a/modules/gltf/doc_classes/GLTFPhysicsShape.xml b/modules/gltf/doc_classes/GLTFPhysicsShape.xml index 67382f3295..c397c660d9 100644 --- a/modules/gltf/doc_classes/GLTFPhysicsShape.xml +++ b/modules/gltf/doc_classes/GLTFPhysicsShape.xml @@ -4,11 +4,12 @@ Represents a GLTF physics shape. </brief_description> <description> - Represents a physics shape as defined by the [code]OMI_collider[/code] GLTF extension. This class is an intermediary between the GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future. + Represents a physics shape as defined by the [code]OMI_physics_shape[/code] or [code]OMI_collider[/code] GLTF extensions. This class is an intermediary between the GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> - <link title="OMI_collider GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_collider</link> + <link title="OMI_physics_shape GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_shape</link> + <link title="OMI_collider GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/Archived/OMI_collider</link> </tutorials> <methods> <method name="from_dictionary" qualifiers="static"> @@ -28,7 +29,7 @@ <method name="to_dictionary" qualifiers="const"> <return type="Dictionary" /> <description> - Serializes this GLTFPhysicsShape instance into a [Dictionary]. + Serializes this GLTFPhysicsShape instance into a [Dictionary] in the format defined by [code]OMI_physics_shape[/code]. </description> </method> <method name="to_node"> diff --git a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp index 2ba5123c31..37b8ae0634 100644 --- a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp +++ b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp @@ -34,13 +34,26 @@ // Import process. Error GLTFDocumentExtensionPhysics::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) { - if (!p_extensions.has("OMI_collider") && !p_extensions.has("OMI_physics_body")) { + if (!p_extensions.has("OMI_collider") && !p_extensions.has("OMI_physics_body") && !p_extensions.has("OMI_physics_shape")) { return ERR_SKIP; } Dictionary state_json = p_state->get_json(); if (state_json.has("extensions")) { Dictionary state_extensions = state_json["extensions"]; - if (state_extensions.has("OMI_collider")) { + if (state_extensions.has("OMI_physics_shape")) { + Dictionary omi_physics_shape_ext = state_extensions["OMI_physics_shape"]; + if (omi_physics_shape_ext.has("shapes")) { + Array state_shape_dicts = omi_physics_shape_ext["shapes"]; + if (state_shape_dicts.size() > 0) { + Array state_shapes; + for (int i = 0; i < state_shape_dicts.size(); i++) { + state_shapes.push_back(GLTFPhysicsShape::from_dictionary(state_shape_dicts[i])); + } + p_state->set_additional_data(StringName("GLTFPhysicsShapes"), state_shapes); + } + } +#ifndef DISABLE_DEPRECATED + } else if (state_extensions.has("OMI_collider")) { Dictionary omi_collider_ext = state_extensions["OMI_collider"]; if (omi_collider_ext.has("colliders")) { Array state_collider_dicts = omi_collider_ext["colliders"]; @@ -49,9 +62,10 @@ Error GLTFDocumentExtensionPhysics::import_preflight(Ref<GLTFState> p_state, Vec for (int i = 0; i < state_collider_dicts.size(); i++) { state_colliders.push_back(GLTFPhysicsShape::from_dictionary(state_collider_dicts[i])); } - p_state->set_additional_data("GLTFPhysicsShapes", state_colliders); + p_state->set_additional_data(StringName("GLTFPhysicsShapes"), state_colliders); } } +#endif // DISABLE_DEPRECATED } } return OK; @@ -61,49 +75,87 @@ Vector<String> GLTFDocumentExtensionPhysics::get_supported_extensions() { Vector<String> ret; ret.push_back("OMI_collider"); ret.push_back("OMI_physics_body"); + ret.push_back("OMI_physics_shape"); return ret; } Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions) { +#ifndef DISABLE_DEPRECATED if (p_extensions.has("OMI_collider")) { Dictionary node_collider_ext = p_extensions["OMI_collider"]; if (node_collider_ext.has("collider")) { // "collider" is the index of the collider in the state colliders array. int node_collider_index = node_collider_ext["collider"]; - Array state_colliders = p_state->get_additional_data("GLTFPhysicsShapes"); + Array state_colliders = p_state->get_additional_data(StringName("GLTFPhysicsShapes")); ERR_FAIL_INDEX_V_MSG(node_collider_index, state_colliders.size(), Error::ERR_FILE_CORRUPT, "GLTF Physics: On node " + p_gltf_node->get_name() + ", the collider index " + itos(node_collider_index) + " is not in the state colliders (size: " + itos(state_colliders.size()) + ")."); p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape"), state_colliders[node_collider_index]); } else { - p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape"), GLTFPhysicsShape::from_dictionary(p_extensions["OMI_collider"])); + p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape"), GLTFPhysicsShape::from_dictionary(node_collider_ext)); } } +#endif // DISABLE_DEPRECATED if (p_extensions.has("OMI_physics_body")) { - p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), GLTFPhysicsBody::from_dictionary(p_extensions["OMI_physics_body"])); + Dictionary physics_body_ext = p_extensions["OMI_physics_body"]; + if (physics_body_ext.has("collider")) { + Dictionary node_collider = physics_body_ext["collider"]; + // "shape" is the index of the shape in the state shapes array. + int node_shape_index = node_collider.get("shape", -1); + if (node_shape_index != -1) { + Array state_shapes = p_state->get_additional_data(StringName("GLTFPhysicsShapes")); + ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "GLTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ")."); + p_gltf_node->set_additional_data(StringName("GLTFPhysicsColliderShape"), state_shapes[node_shape_index]); + } else { + // If this node is a collider but does not have a collider + // shape, then it only serves to combine together shapes. + p_gltf_node->set_additional_data(StringName("GLTFPhysicsCompoundCollider"), true); + } + } + if (physics_body_ext.has("trigger")) { + Dictionary node_trigger = physics_body_ext["trigger"]; + // "shape" is the index of the shape in the state shapes array. + int node_shape_index = node_trigger.get("shape", -1); + if (node_shape_index != -1) { + Array state_shapes = p_state->get_additional_data(StringName("GLTFPhysicsShapes")); + ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "GLTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ")."); + p_gltf_node->set_additional_data(StringName("GLTFPhysicsTriggerShape"), state_shapes[node_shape_index]); + } else { + // If this node is a trigger but does not have a trigger shape, + // then it's a trigger body, what Godot calls an Area3D node. + Ref<GLTFPhysicsBody> trigger_body; + trigger_body.instantiate(); + trigger_body->set_body_type("trigger"); + p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), trigger_body); + } + } + if (physics_body_ext.has("motion") || physics_body_ext.has("type")) { + p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), GLTFPhysicsBody::from_dictionary(physics_body_ext)); + } } return OK; } -void _setup_collider_mesh_resource_from_index_if_needed(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_collider) { - GLTFMeshIndex collider_mesh_index = p_collider->get_mesh_index(); - if (collider_mesh_index == -1) { - return; // No mesh for this collider. +void _setup_shape_mesh_resource_from_index_if_needed(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_gltf_shape) { + GLTFMeshIndex shape_mesh_index = p_gltf_shape->get_mesh_index(); + if (shape_mesh_index == -1) { + return; // No mesh for this shape. } - Ref<ImporterMesh> importer_mesh = p_collider->get_importer_mesh(); + Ref<ImporterMesh> importer_mesh = p_gltf_shape->get_importer_mesh(); if (importer_mesh.is_valid()) { return; // The mesh resource is already set up. } TypedArray<GLTFMesh> state_meshes = p_state->get_meshes(); - ERR_FAIL_INDEX_MSG(collider_mesh_index, state_meshes.size(), "GLTF Physics: When importing '" + p_state->get_scene_name() + "', the collider mesh index " + itos(collider_mesh_index) + " is not in the state meshes (size: " + itos(state_meshes.size()) + ")."); - Ref<GLTFMesh> gltf_mesh = state_meshes[collider_mesh_index]; + ERR_FAIL_INDEX_MSG(shape_mesh_index, state_meshes.size(), "GLTF Physics: When importing '" + p_state->get_scene_name() + "', the shape mesh index " + itos(shape_mesh_index) + " is not in the state meshes (size: " + itos(state_meshes.size()) + ")."); + Ref<GLTFMesh> gltf_mesh = state_meshes[shape_mesh_index]; ERR_FAIL_COND(gltf_mesh.is_null()); importer_mesh = gltf_mesh->get_mesh(); ERR_FAIL_COND(importer_mesh.is_null()); - p_collider->set_importer_mesh(importer_mesh); + p_gltf_shape->set_importer_mesh(importer_mesh); } -CollisionObject3D *_generate_collision_with_body(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Ref<GLTFPhysicsShape> p_collider, Ref<GLTFPhysicsBody> p_physics_body) { - print_verbose("glTF: Creating collision for: " + p_gltf_node->get_name()); - bool is_trigger = p_collider->get_is_trigger(); +#ifndef DISABLE_DEPRECATED +CollisionObject3D *_generate_shape_with_body(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Ref<GLTFPhysicsShape> p_physics_shape, Ref<GLTFPhysicsBody> p_physics_body) { + print_verbose("glTF: Creating shape with body for: " + p_gltf_node->get_name()); + bool is_trigger = p_physics_shape->get_is_trigger(); // This method is used for the case where we must generate a parent body. // This is can happen for multiple reasons. One possibility is that this // GLTF file is using OMI_collider but not OMI_physics_body, or at least @@ -113,10 +165,10 @@ CollisionObject3D *_generate_collision_with_body(Ref<GLTFState> p_state, Ref<GLT if (p_physics_body.is_valid()) { // This code is run when the physics body is on the same GLTF node. body = p_physics_body->to_node(); - if (is_trigger != (p_physics_body->get_body_type() == "trigger")) { + if (is_trigger && (p_physics_body->get_body_type() != "trigger")) { // Edge case: If the body's trigger and the collider's trigger // are in disagreement, we need to create another new body. - CollisionObject3D *child = _generate_collision_with_body(p_state, p_gltf_node, p_collider, nullptr); + CollisionObject3D *child = _generate_shape_with_body(p_state, p_gltf_node, p_physics_shape, nullptr); child->set_name(p_gltf_node->get_name() + (is_trigger ? String("Trigger") : String("Solid"))); body->add_child(child); return body; @@ -126,33 +178,131 @@ CollisionObject3D *_generate_collision_with_body(Ref<GLTFState> p_state, Ref<GLT } else { body = memnew(StaticBody3D); } - CollisionShape3D *shape = p_collider->to_node(); + CollisionShape3D *shape = p_physics_shape->to_node(); shape->set_name(p_gltf_node->get_name() + "Shape"); body->add_child(shape); return body; } +#endif // DISABLE_DEPRECATED + +CollisionObject3D *_get_ancestor_collision_object(Node *p_scene_parent) { + // Note: Despite the name of the method, at the moment this only checks + // the direct parent. Only check more later if Godot adds support for it. + if (p_scene_parent) { + CollisionObject3D *co = Object::cast_to<CollisionObject3D>(p_scene_parent); + if (likely(co)) { + return co; + } + } + return nullptr; +} + +Node3D *_generate_shape_node_and_body_if_needed(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Ref<GLTFPhysicsShape> p_physics_shape, CollisionObject3D *p_col_object, bool p_is_trigger) { + // If we need to generate a body node, do so. + CollisionObject3D *body_node = nullptr; + if (p_is_trigger || p_physics_shape->get_is_trigger()) { + // If the shape wants to be a trigger but it doesn't + // have an Area3D parent, we need to make one. + if (!Object::cast_to<Area3D>(p_col_object)) { + body_node = memnew(Area3D); + } + } else { + if (!Object::cast_to<PhysicsBody3D>(p_col_object)) { + body_node = memnew(StaticBody3D); + } + } + // Generate the shape node. + _setup_shape_mesh_resource_from_index_if_needed(p_state, p_physics_shape); + CollisionShape3D *shape_node = p_physics_shape->to_node(true); + if (body_node) { + shape_node->set_name(p_gltf_node->get_name() + "Shape"); + body_node->add_child(shape_node); + return body_node; + } + return shape_node; +} + +// Either add the child to the parent, or return the child if there is no parent. +Node3D *_add_physics_node_to_given_node(Node3D *p_current_node, Node3D *p_child, Ref<GLTFNode> p_gltf_node) { + if (!p_current_node) { + return p_child; + } + String suffix; + if (Object::cast_to<CollisionShape3D>(p_child)) { + suffix = "Shape"; + } else if (Object::cast_to<Area3D>(p_child)) { + suffix = "Trigger"; + } else { + suffix = "Collider"; + } + p_child->set_name(p_gltf_node->get_name() + suffix); + p_current_node->add_child(p_child); + return p_current_node; +} Node3D *GLTFDocumentExtensionPhysics::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) { - Ref<GLTFPhysicsBody> physics_body = p_gltf_node->get_additional_data(StringName("GLTFPhysicsBody")); - Ref<GLTFPhysicsShape> collider = p_gltf_node->get_additional_data(StringName("GLTFPhysicsShape")); - if (collider.is_valid()) { - _setup_collider_mesh_resource_from_index_if_needed(p_state, collider); - // If the collider has the correct type of parent, we just return one node. - if (collider->get_is_trigger()) { - if (Object::cast_to<Area3D>(p_scene_parent)) { - return collider->to_node(true); + Ref<GLTFPhysicsBody> gltf_physics_body = p_gltf_node->get_additional_data(StringName("GLTFPhysicsBody")); +#ifndef DISABLE_DEPRECATED + // This deprecated code handles OMI_collider (which we internally name "GLTFPhysicsShape"). + Ref<GLTFPhysicsShape> gltf_physics_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsShape")); + if (gltf_physics_shape.is_valid()) { + _setup_shape_mesh_resource_from_index_if_needed(p_state, gltf_physics_shape); + // If this GLTF node specifies both a shape and a body, generate both. + if (gltf_physics_body.is_valid()) { + return _generate_shape_with_body(p_state, p_gltf_node, gltf_physics_shape, gltf_physics_body); + } + CollisionObject3D *ancestor_col_obj = _get_ancestor_collision_object(p_scene_parent); + if (gltf_physics_shape->get_is_trigger()) { + // If the shape wants to be a trigger and it already has a + // trigger parent, we only need to make the shape node. + if (Object::cast_to<Area3D>(ancestor_col_obj)) { + return gltf_physics_shape->to_node(true); } - } else { - if (Object::cast_to<PhysicsBody3D>(p_scene_parent)) { - return collider->to_node(true); + } else if (ancestor_col_obj != nullptr) { + // If the shape has a valid parent, only make the shape node. + return gltf_physics_shape->to_node(true); + } + // Otherwise, we need to create a new body. + return _generate_shape_with_body(p_state, p_gltf_node, gltf_physics_shape, nullptr); + } +#endif // DISABLE_DEPRECATED + Node3D *ret = nullptr; + CollisionObject3D *ancestor_col_obj = nullptr; + if (gltf_physics_body.is_valid()) { + ancestor_col_obj = gltf_physics_body->to_node(); + ret = ancestor_col_obj; + } else { + ancestor_col_obj = _get_ancestor_collision_object(p_scene_parent); + if (!Object::cast_to<PhysicsBody3D>(ancestor_col_obj)) { + if (p_gltf_node->get_additional_data(StringName("GLTFPhysicsCompoundCollider"))) { + // If the GLTF file wants this node to group solid shapes together, + // and there is no parent body, we need to create a static body. + ancestor_col_obj = memnew(StaticBody3D); + ret = ancestor_col_obj; } } - return _generate_collision_with_body(p_state, p_gltf_node, collider, physics_body); } - if (physics_body.is_valid()) { - return physics_body->to_node(); + // Add the shapes to the tree. When an ancestor body is present, use it. + // If an explicit body was specified, it has already been generated and + // set above. If there is no ancestor body, we will either generate an + // Area3D or StaticBody3D implicitly, so prefer an Area3D as the base + // node for best compatibility with signal connections to this node. + Ref<GLTFPhysicsShape> gltf_physics_collider_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape")); + Ref<GLTFPhysicsShape> gltf_physics_trigger_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShape")); + bool is_ancestor_col_obj_solid = Object::cast_to<PhysicsBody3D>(ancestor_col_obj); + if (is_ancestor_col_obj_solid && gltf_physics_collider_shape.is_valid()) { + Node3D *child = _generate_shape_node_and_body_if_needed(p_state, p_gltf_node, gltf_physics_collider_shape, ancestor_col_obj, false); + ret = _add_physics_node_to_given_node(ret, child, p_gltf_node); } - return nullptr; + if (gltf_physics_trigger_shape.is_valid()) { + Node3D *child = _generate_shape_node_and_body_if_needed(p_state, p_gltf_node, gltf_physics_trigger_shape, ancestor_col_obj, true); + ret = _add_physics_node_to_given_node(ret, child, p_gltf_node); + } + if (!is_ancestor_col_obj_solid && gltf_physics_collider_shape.is_valid()) { + Node3D *child = _generate_shape_node_and_body_if_needed(p_state, p_gltf_node, gltf_physics_collider_shape, ancestor_col_obj, false); + ret = _add_physics_node_to_given_node(ret, child, p_gltf_node); + } + return ret; } // Export process. @@ -202,22 +352,26 @@ GLTFMeshIndex _get_or_insert_mesh_in_state(Ref<GLTFState> p_state, Ref<ImporterM void GLTFDocumentExtensionPhysics::convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node) { if (cast_to<CollisionShape3D>(p_scene_node)) { - CollisionShape3D *shape = Object::cast_to<CollisionShape3D>(p_scene_node); - Ref<GLTFPhysicsShape> collider = GLTFPhysicsShape::from_node(shape); + CollisionShape3D *godot_shape = Object::cast_to<CollisionShape3D>(p_scene_node); + Ref<GLTFPhysicsShape> gltf_shape = GLTFPhysicsShape::from_node(godot_shape); { - Ref<ImporterMesh> importer_mesh = collider->get_importer_mesh(); + Ref<ImporterMesh> importer_mesh = gltf_shape->get_importer_mesh(); if (importer_mesh.is_valid()) { - collider->set_mesh_index(_get_or_insert_mesh_in_state(p_state, importer_mesh)); + gltf_shape->set_mesh_index(_get_or_insert_mesh_in_state(p_state, importer_mesh)); } } - p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape"), collider); + if (cast_to<Area3D>(_get_ancestor_collision_object(p_scene_node))) { + p_gltf_node->set_additional_data(StringName("GLTFPhysicsTriggerShape"), gltf_shape); + } else { + p_gltf_node->set_additional_data(StringName("GLTFPhysicsColliderShape"), gltf_shape); + } } else if (cast_to<CollisionObject3D>(p_scene_node)) { - CollisionObject3D *body = Object::cast_to<CollisionObject3D>(p_scene_node); - p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), GLTFPhysicsBody::from_node(body)); + CollisionObject3D *godot_body = Object::cast_to<CollisionObject3D>(p_scene_node); + p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), GLTFPhysicsBody::from_node(godot_body)); } } -Array _get_or_create_state_colliders_in_state(Ref<GLTFState> p_state) { +Array _get_or_create_state_shapes_in_state(Ref<GLTFState> p_state) { Dictionary state_json = p_state->get_json(); Dictionary state_extensions; if (state_json.has("extensions")) { @@ -225,48 +379,60 @@ Array _get_or_create_state_colliders_in_state(Ref<GLTFState> p_state) { } else { state_json["extensions"] = state_extensions; } - Dictionary omi_collider_ext; - if (state_extensions.has("OMI_collider")) { - omi_collider_ext = state_extensions["OMI_collider"]; + Dictionary omi_physics_shape_ext; + if (state_extensions.has("OMI_physics_shape")) { + omi_physics_shape_ext = state_extensions["OMI_physics_shape"]; } else { - state_extensions["OMI_collider"] = omi_collider_ext; - p_state->add_used_extension("OMI_collider"); + state_extensions["OMI_physics_shape"] = omi_physics_shape_ext; + p_state->add_used_extension("OMI_physics_shape"); } - Array state_colliders; - if (omi_collider_ext.has("colliders")) { - state_colliders = omi_collider_ext["colliders"]; + Array state_shapes; + if (omi_physics_shape_ext.has("shapes")) { + state_shapes = omi_physics_shape_ext["shapes"]; } else { - omi_collider_ext["colliders"] = state_colliders; + omi_physics_shape_ext["shapes"] = state_shapes; + } + return state_shapes; +} + +Dictionary _export_node_shape(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_physics_shape) { + Array state_shapes = _get_or_create_state_shapes_in_state(p_state); + int size = state_shapes.size(); + Dictionary shape_property; + Dictionary shape_dict = p_physics_shape->to_dictionary(); + for (int i = 0; i < size; i++) { + Dictionary other = state_shapes[i]; + if (other == shape_dict) { + // De-duplication: If we already have an identical shape, + // set the shape index to the existing one and return. + shape_property["shape"] = i; + return shape_property; + } } - return state_colliders; + // If we don't have an identical shape, add it to the array. + state_shapes.push_back(shape_dict); + shape_property["shape"] = size; + return shape_property; } Error GLTFDocumentExtensionPhysics::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_node_json, Node *p_node) { - Dictionary node_extensions = r_node_json["extensions"]; + Dictionary physics_body_ext; Ref<GLTFPhysicsBody> physics_body = p_gltf_node->get_additional_data(StringName("GLTFPhysicsBody")); if (physics_body.is_valid()) { - node_extensions["OMI_physics_body"] = physics_body->to_dictionary(); - p_state->add_used_extension("OMI_physics_body"); + physics_body_ext = physics_body->to_dictionary(); } - Ref<GLTFPhysicsShape> collider = p_gltf_node->get_additional_data(StringName("GLTFPhysicsShape")); - if (collider.is_valid()) { - Array state_colliders = _get_or_create_state_colliders_in_state(p_state); - int size = state_colliders.size(); - Dictionary omi_collider_ext; - node_extensions["OMI_collider"] = omi_collider_ext; - Dictionary collider_dict = collider->to_dictionary(); - for (int i = 0; i < size; i++) { - Dictionary other = state_colliders[i]; - if (other == collider_dict) { - // De-duplication: If we already have an identical collider, - // set the collider index to the existing one and return. - omi_collider_ext["collider"] = i; - return OK; - } - } - // If we don't have an identical collider, add it to the array. - state_colliders.push_back(collider_dict); - omi_collider_ext["collider"] = size; + Ref<GLTFPhysicsShape> collider_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape")); + if (collider_shape.is_valid()) { + physics_body_ext["collider"] = _export_node_shape(p_state, collider_shape); + } + Ref<GLTFPhysicsShape> trigger_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShape")); + if (trigger_shape.is_valid()) { + physics_body_ext["trigger"] = _export_node_shape(p_state, trigger_shape); + } + if (!physics_body_ext.is_empty()) { + Dictionary node_extensions = r_node_json["extensions"]; + node_extensions["OMI_physics_body"] = physics_body_ext; + p_state->add_used_extension("OMI_physics_body"); } return OK; } diff --git a/modules/gltf/extensions/physics/gltf_physics_body.cpp b/modules/gltf/extensions/physics/gltf_physics_body.cpp index b80f4348c2..271bb9b332 100644 --- a/modules/gltf/extensions/physics/gltf_physics_body.cpp +++ b/modules/gltf/extensions/physics/gltf_physics_body.cpp @@ -50,22 +50,70 @@ void GLTFPhysicsBody::_bind_methods() { ClassDB::bind_method(D_METHOD("set_angular_velocity", "angular_velocity"), &GLTFPhysicsBody::set_angular_velocity); ClassDB::bind_method(D_METHOD("get_center_of_mass"), &GLTFPhysicsBody::get_center_of_mass); ClassDB::bind_method(D_METHOD("set_center_of_mass", "center_of_mass"), &GLTFPhysicsBody::set_center_of_mass); + ClassDB::bind_method(D_METHOD("get_inertia_diagonal"), &GLTFPhysicsBody::get_inertia_diagonal); + ClassDB::bind_method(D_METHOD("set_inertia_diagonal", "inertia_diagonal"), &GLTFPhysicsBody::set_inertia_diagonal); + ClassDB::bind_method(D_METHOD("get_inertia_orientation"), &GLTFPhysicsBody::get_inertia_orientation); + ClassDB::bind_method(D_METHOD("set_inertia_orientation", "inertia_orientation"), &GLTFPhysicsBody::set_inertia_orientation); +#ifndef DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("get_inertia_tensor"), &GLTFPhysicsBody::get_inertia_tensor); ClassDB::bind_method(D_METHOD("set_inertia_tensor", "inertia_tensor"), &GLTFPhysicsBody::set_inertia_tensor); +#endif // DISABLE_DEPRECATED ADD_PROPERTY(PropertyInfo(Variant::STRING, "body_type"), "set_body_type", "get_body_type"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass"), "set_mass", "get_mass"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "angular_velocity"), "set_angular_velocity", "get_angular_velocity"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_of_mass"), "set_center_of_mass", "get_center_of_mass"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "inertia_diagonal"), "set_inertia_diagonal", "get_inertia_diagonal"); + ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "inertia_orientation"), "set_inertia_orientation", "get_inertia_orientation"); +#ifndef DISABLE_DEPRECATED ADD_PROPERTY(PropertyInfo(Variant::BASIS, "inertia_tensor"), "set_inertia_tensor", "get_inertia_tensor"); +#endif // DISABLE_DEPRECATED } String GLTFPhysicsBody::get_body_type() const { - return body_type; + switch (body_type) { + case PhysicsBodyType::STATIC: + return "static"; + case PhysicsBodyType::ANIMATABLE: + return "animatable"; + case PhysicsBodyType::CHARACTER: + return "character"; + case PhysicsBodyType::RIGID: + return "rigid"; + case PhysicsBodyType::VEHICLE: + return "vehicle"; + case PhysicsBodyType::TRIGGER: + return "trigger"; + } + // Unreachable, the switch cases handle all values the enum can take. + // Omitting this works on Clang but not GCC or MSVC. If reached, it's UB. + return "rigid"; } void GLTFPhysicsBody::set_body_type(String p_body_type) { + if (p_body_type == "static") { + body_type = PhysicsBodyType::STATIC; + } else if (p_body_type == "animatable") { + body_type = PhysicsBodyType::ANIMATABLE; + } else if (p_body_type == "character") { + body_type = PhysicsBodyType::CHARACTER; + } else if (p_body_type == "rigid") { + body_type = PhysicsBodyType::RIGID; + } else if (p_body_type == "vehicle") { + body_type = PhysicsBodyType::VEHICLE; + } else if (p_body_type == "trigger") { + body_type = PhysicsBodyType::TRIGGER; + } else { + ERR_PRINT("Error setting GLTF physics body type: The body type must be one of \"static\", \"animatable\", \"character\", \"rigid\", \"vehicle\", or \"trigger\"."); + } +} + +GLTFPhysicsBody::PhysicsBodyType GLTFPhysicsBody::get_physics_body_type() const { + return body_type; +} + +void GLTFPhysicsBody::set_physics_body_type(PhysicsBodyType p_body_type) { body_type = p_body_type; } @@ -101,140 +149,215 @@ void GLTFPhysicsBody::set_center_of_mass(const Vector3 &p_center_of_mass) { center_of_mass = p_center_of_mass; } +Vector3 GLTFPhysicsBody::get_inertia_diagonal() const { + return inertia_diagonal; +} + +void GLTFPhysicsBody::set_inertia_diagonal(const Vector3 &p_inertia_diagonal) { + inertia_diagonal = p_inertia_diagonal; +} + +Quaternion GLTFPhysicsBody::get_inertia_orientation() const { + return inertia_orientation; +} + +void GLTFPhysicsBody::set_inertia_orientation(const Quaternion &p_inertia_orientation) { + inertia_orientation = p_inertia_orientation; +} + +#ifndef DISABLE_DEPRECATED Basis GLTFPhysicsBody::get_inertia_tensor() const { - return inertia_tensor; + return Basis::from_scale(inertia_diagonal); } void GLTFPhysicsBody::set_inertia_tensor(Basis p_inertia_tensor) { - inertia_tensor = p_inertia_tensor; + inertia_diagonal = p_inertia_tensor.get_main_diagonal(); } +#endif // DISABLE_DEPRECATED Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_node(const CollisionObject3D *p_body_node) { Ref<GLTFPhysicsBody> physics_body; physics_body.instantiate(); ERR_FAIL_NULL_V_MSG(p_body_node, physics_body, "Tried to create a GLTFPhysicsBody from a CollisionObject3D node, but the given node was null."); if (cast_to<CharacterBody3D>(p_body_node)) { - physics_body->body_type = "character"; + physics_body->body_type = PhysicsBodyType::CHARACTER; } else if (cast_to<AnimatableBody3D>(p_body_node)) { - physics_body->body_type = "kinematic"; + physics_body->body_type = PhysicsBodyType::ANIMATABLE; } else if (cast_to<RigidBody3D>(p_body_node)) { const RigidBody3D *body = cast_to<const RigidBody3D>(p_body_node); physics_body->mass = body->get_mass(); physics_body->linear_velocity = body->get_linear_velocity(); physics_body->angular_velocity = body->get_angular_velocity(); physics_body->center_of_mass = body->get_center_of_mass(); - Vector3 inertia_diagonal = body->get_inertia(); - physics_body->inertia_tensor = Basis(inertia_diagonal.x, 0, 0, 0, inertia_diagonal.y, 0, 0, 0, inertia_diagonal.z); + physics_body->inertia_diagonal = body->get_inertia(); if (body->get_center_of_mass() != Vector3()) { WARN_PRINT("GLTFPhysicsBody: This rigid body has a center of mass offset from the origin, which will be ignored when exporting to GLTF."); } if (cast_to<VehicleBody3D>(p_body_node)) { - physics_body->body_type = "vehicle"; + physics_body->body_type = PhysicsBodyType::VEHICLE; } else { - physics_body->body_type = "rigid"; + physics_body->body_type = PhysicsBodyType::RIGID; } } else if (cast_to<StaticBody3D>(p_body_node)) { - physics_body->body_type = "static"; + physics_body->body_type = PhysicsBodyType::STATIC; } else if (cast_to<Area3D>(p_body_node)) { - physics_body->body_type = "trigger"; + physics_body->body_type = PhysicsBodyType::TRIGGER; } return physics_body; } CollisionObject3D *GLTFPhysicsBody::to_node() const { - if (body_type == "character") { - CharacterBody3D *body = memnew(CharacterBody3D); - return body; - } - if (body_type == "kinematic") { - AnimatableBody3D *body = memnew(AnimatableBody3D); - return body; - } - if (body_type == "vehicle") { - VehicleBody3D *body = memnew(VehicleBody3D); - body->set_mass(mass); - body->set_linear_velocity(linear_velocity); - body->set_angular_velocity(angular_velocity); - body->set_inertia(inertia_tensor.get_main_diagonal()); - body->set_center_of_mass_mode(RigidBody3D::CENTER_OF_MASS_MODE_CUSTOM); - body->set_center_of_mass(center_of_mass); - return body; - } - if (body_type == "rigid") { - RigidBody3D *body = memnew(RigidBody3D); - body->set_mass(mass); - body->set_linear_velocity(linear_velocity); - body->set_angular_velocity(angular_velocity); - body->set_inertia(inertia_tensor.get_main_diagonal()); - body->set_center_of_mass_mode(RigidBody3D::CENTER_OF_MASS_MODE_CUSTOM); - body->set_center_of_mass(center_of_mass); - return body; - } - if (body_type == "static") { - StaticBody3D *body = memnew(StaticBody3D); - return body; - } - if (body_type == "trigger") { - Area3D *body = memnew(Area3D); - return body; + switch (body_type) { + case PhysicsBodyType::CHARACTER: { + CharacterBody3D *body = memnew(CharacterBody3D); + return body; + } + case PhysicsBodyType::ANIMATABLE: { + AnimatableBody3D *body = memnew(AnimatableBody3D); + return body; + } + case PhysicsBodyType::VEHICLE: { + VehicleBody3D *body = memnew(VehicleBody3D); + body->set_mass(mass); + body->set_linear_velocity(linear_velocity); + body->set_angular_velocity(angular_velocity); + body->set_inertia(inertia_diagonal); + body->set_center_of_mass_mode(RigidBody3D::CENTER_OF_MASS_MODE_CUSTOM); + body->set_center_of_mass(center_of_mass); + return body; + } + case PhysicsBodyType::RIGID: { + RigidBody3D *body = memnew(RigidBody3D); + body->set_mass(mass); + body->set_linear_velocity(linear_velocity); + body->set_angular_velocity(angular_velocity); + body->set_inertia(inertia_diagonal); + body->set_center_of_mass_mode(RigidBody3D::CENTER_OF_MASS_MODE_CUSTOM); + body->set_center_of_mass(center_of_mass); + return body; + } + case PhysicsBodyType::STATIC: { + StaticBody3D *body = memnew(StaticBody3D); + return body; + } + case PhysicsBodyType::TRIGGER: { + Area3D *body = memnew(Area3D); + return body; + } } - ERR_FAIL_V_MSG(nullptr, "Error converting GLTFPhysicsBody to a node: Body type '" + body_type + "' is unknown."); + // Unreachable, the switch cases handle all values the enum can take. + // Omitting this works on Clang but not GCC or MSVC. If reached, it's UB. + return nullptr; } Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_dictionary) { Ref<GLTFPhysicsBody> physics_body; physics_body.instantiate(); - ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), physics_body, "Failed to parse GLTF physics body, missing required field 'type'."); - const String &body_type = p_dictionary["type"]; - physics_body->body_type = body_type; - - if (p_dictionary.has("mass")) { - physics_body->mass = p_dictionary["mass"]; + Dictionary motion; + if (p_dictionary.has("motion")) { + motion = p_dictionary["motion"]; +#ifndef DISABLE_DEPRECATED + } else { + motion = p_dictionary; +#endif // DISABLE_DEPRECATED } - if (p_dictionary.has("linearVelocity")) { - const Array &arr = p_dictionary["linearVelocity"]; + if (motion.has("type")) { + // Read the body type. This representation sits between glTF's and Godot's physics nodes. + // While we may only read "static", "kinematic", or "dynamic" from a valid glTF file, we + // want to allow another extension to override this to another Godot node type mid-import. + // For example, a vehicle extension may want to override the body type to "vehicle" + // so Godot generates a VehicleBody3D node. Therefore we distinguish by importing + // "dynamic" as "rigid", and "kinematic" as "animatable", in the GLTFPhysicsBody code. + String body_type_string = motion["type"]; + if (body_type_string == "static") { + physics_body->body_type = PhysicsBodyType::STATIC; + } else if (body_type_string == "kinematic") { + physics_body->body_type = PhysicsBodyType::ANIMATABLE; + } else if (body_type_string == "dynamic") { + physics_body->body_type = PhysicsBodyType::RIGID; +#ifndef DISABLE_DEPRECATED + } else if (body_type_string == "character") { + physics_body->body_type = PhysicsBodyType::CHARACTER; + } else if (body_type_string == "rigid") { + physics_body->body_type = PhysicsBodyType::RIGID; + } else if (body_type_string == "vehicle") { + physics_body->body_type = PhysicsBodyType::VEHICLE; + } else if (body_type_string == "trigger") { + physics_body->body_type = PhysicsBodyType::TRIGGER; +#endif // DISABLE_DEPRECATED + } else { + ERR_PRINT("Error parsing GLTF physics body: The body type in the GLTF file \"" + body_type_string + "\" was not recognized."); + } + } + if (motion.has("mass")) { + physics_body->mass = motion["mass"]; + } + if (motion.has("linearVelocity")) { + const Array &arr = motion["linearVelocity"]; if (arr.size() == 3) { physics_body->set_linear_velocity(Vector3(arr[0], arr[1], arr[2])); } else { ERR_PRINT("Error parsing GLTF physics body: The linear velocity vector must have exactly 3 numbers."); } } - if (p_dictionary.has("angularVelocity")) { - const Array &arr = p_dictionary["angularVelocity"]; + if (motion.has("angularVelocity")) { + const Array &arr = motion["angularVelocity"]; if (arr.size() == 3) { physics_body->set_angular_velocity(Vector3(arr[0], arr[1], arr[2])); } else { ERR_PRINT("Error parsing GLTF physics body: The angular velocity vector must have exactly 3 numbers."); } } - if (p_dictionary.has("centerOfMass")) { - const Array &arr = p_dictionary["centerOfMass"]; + if (motion.has("centerOfMass")) { + const Array &arr = motion["centerOfMass"]; if (arr.size() == 3) { physics_body->set_center_of_mass(Vector3(arr[0], arr[1], arr[2])); } else { ERR_PRINT("Error parsing GLTF physics body: The center of mass vector must have exactly 3 numbers."); } } - if (p_dictionary.has("inertiaTensor")) { - const Array &arr = p_dictionary["inertiaTensor"]; - if (arr.size() == 9) { - // Only use the diagonal elements of the inertia tensor matrix (principal axes). - physics_body->set_inertia_tensor(Basis(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6], arr[7], arr[8])); + if (motion.has("inertiaDiagonal")) { + const Array &arr = motion["inertiaDiagonal"]; + if (arr.size() == 3) { + physics_body->set_inertia_diagonal(Vector3(arr[0], arr[1], arr[2])); } else { - ERR_PRINT("Error parsing GLTF physics body: The inertia tensor must be a 3x3 matrix (9 number array)."); + ERR_PRINT("Error parsing GLTF physics body: The inertia diagonal vector must have exactly 3 numbers."); } } - if (body_type != "character" && body_type != "kinematic" && body_type != "rigid" && body_type != "static" && body_type != "trigger" && body_type != "vehicle") { - ERR_PRINT("Error parsing GLTF physics body: Body type '" + body_type + "' is unknown."); + if (motion.has("inertiaOrientation")) { + const Array &arr = motion["inertiaOrientation"]; + if (arr.size() == 4) { + physics_body->set_inertia_orientation(Quaternion(arr[0], arr[1], arr[2], arr[3])); + } else { + ERR_PRINT("Error parsing GLTF physics body: The inertia orientation quaternion must have exactly 4 numbers."); + } } return physics_body; } Dictionary GLTFPhysicsBody::to_dictionary() const { - Dictionary d; - d["type"] = body_type; + Dictionary ret; + if (body_type == PhysicsBodyType::TRIGGER) { + // The equivalent of a Godot Area3D node in glTF is a node that + // defines that it is a trigger, but does not have a shape. + Dictionary trigger; + ret["trigger"] = trigger; + return ret; + } + // All non-trigger body types are defined using the motion property. + Dictionary motion; + // When stored in memory, the body type can correspond to a Godot + // node type. However, when exporting to glTF, we need to squash + // this down to one of "static", "kinematic", or "dynamic". + if (body_type == PhysicsBodyType::STATIC) { + motion["type"] = "static"; + } else if (body_type == PhysicsBodyType::ANIMATABLE || body_type == PhysicsBodyType::CHARACTER) { + motion["type"] = "kinematic"; + } else { + motion["type"] = "dynamic"; + } if (mass != 1.0) { - d["mass"] = mass; + motion["mass"] = mass; } if (linear_velocity != Vector3()) { Array velocity_array; @@ -242,7 +365,7 @@ Dictionary GLTFPhysicsBody::to_dictionary() const { velocity_array[0] = linear_velocity.x; velocity_array[1] = linear_velocity.y; velocity_array[2] = linear_velocity.z; - d["linearVelocity"] = velocity_array; + motion["linearVelocity"] = velocity_array; } if (angular_velocity != Vector3()) { Array velocity_array; @@ -250,7 +373,7 @@ Dictionary GLTFPhysicsBody::to_dictionary() const { velocity_array[0] = angular_velocity.x; velocity_array[1] = angular_velocity.y; velocity_array[2] = angular_velocity.z; - d["angularVelocity"] = velocity_array; + motion["angularVelocity"] = velocity_array; } if (center_of_mass != Vector3()) { Array center_of_mass_array; @@ -258,22 +381,25 @@ Dictionary GLTFPhysicsBody::to_dictionary() const { center_of_mass_array[0] = center_of_mass.x; center_of_mass_array[1] = center_of_mass.y; center_of_mass_array[2] = center_of_mass.z; - d["centerOfMass"] = center_of_mass_array; + motion["centerOfMass"] = center_of_mass_array; + } + if (inertia_diagonal != Vector3()) { + Array inertia_array; + inertia_array.resize(3); + inertia_array[0] = inertia_diagonal[0]; + inertia_array[1] = inertia_diagonal[1]; + inertia_array[2] = inertia_diagonal[2]; + motion["inertiaDiagonal"] = inertia_array; } - if (inertia_tensor != Basis(0, 0, 0, 0, 0, 0, 0, 0, 0)) { + if (inertia_orientation != Quaternion()) { Array inertia_array; - inertia_array.resize(9); - inertia_array.fill(0.0); - inertia_array[0] = inertia_tensor[0][0]; - inertia_array[1] = inertia_tensor[0][1]; - inertia_array[2] = inertia_tensor[0][2]; - inertia_array[3] = inertia_tensor[1][0]; - inertia_array[4] = inertia_tensor[1][1]; - inertia_array[5] = inertia_tensor[1][2]; - inertia_array[6] = inertia_tensor[2][0]; - inertia_array[7] = inertia_tensor[2][1]; - inertia_array[8] = inertia_tensor[2][2]; - d["inertiaTensor"] = inertia_array; + inertia_array.resize(4); + inertia_array[0] = inertia_orientation[0]; + inertia_array[1] = inertia_orientation[1]; + inertia_array[2] = inertia_orientation[2]; + inertia_array[3] = inertia_orientation[3]; + motion["inertiaDiagonal"] = inertia_array; } - return d; + ret["motion"] = motion; + return ret; } diff --git a/modules/gltf/extensions/physics/gltf_physics_body.h b/modules/gltf/extensions/physics/gltf_physics_body.h index 391b4b873f..6b21639a7b 100644 --- a/modules/gltf/extensions/physics/gltf_physics_body.h +++ b/modules/gltf/extensions/physics/gltf_physics_body.h @@ -33,27 +33,47 @@ #include "scene/3d/physics_body_3d.h" -// GLTFPhysicsBody is an intermediary between OMI_physics_body and Godot's physics body nodes. +// GLTFPhysicsBody is an intermediary between Godot's physics body nodes +// and the OMI_physics_body extension. // https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_body class GLTFPhysicsBody : public Resource { GDCLASS(GLTFPhysicsBody, Resource) +public: + // These values map to Godot's physics body types. + // When importing, the body type will be set to the closest match, and + // user code can change this to make Godot generate a different node type. + // When exporting, this will be squashed down to one of "static", + // "kinematic", or "dynamic" motion types, or the "trigger" property. + enum class PhysicsBodyType { + STATIC, + ANIMATABLE, + CHARACTER, + RIGID, + VEHICLE, + TRIGGER, + }; + protected: static void _bind_methods(); private: - String body_type = "static"; + PhysicsBodyType body_type = PhysicsBodyType::RIGID; real_t mass = 1.0; Vector3 linear_velocity; Vector3 angular_velocity; Vector3 center_of_mass; - Basis inertia_tensor = Basis(0, 0, 0, 0, 0, 0, 0, 0, 0); + Vector3 inertia_diagonal; + Quaternion inertia_orientation; public: String get_body_type() const; void set_body_type(String p_body_type); + PhysicsBodyType get_physics_body_type() const; + void set_physics_body_type(PhysicsBodyType p_body_type); + real_t get_mass() const; void set_mass(real_t p_mass); @@ -66,8 +86,16 @@ public: Vector3 get_center_of_mass() const; void set_center_of_mass(const Vector3 &p_center_of_mass); + Vector3 get_inertia_diagonal() const; + void set_inertia_diagonal(const Vector3 &p_inertia_diagonal); + + Quaternion get_inertia_orientation() const; + void set_inertia_orientation(const Quaternion &p_inertia_orientation); + +#ifndef DISABLE_DEPRECATED Basis get_inertia_tensor() const; void set_inertia_tensor(Basis p_inertia_tensor); +#endif // DISABLE_DEPRECATED static Ref<GLTFPhysicsBody> from_node(const CollisionObject3D *p_body_node); CollisionObject3D *to_node() const; diff --git a/modules/gltf/extensions/physics/gltf_physics_shape.cpp b/modules/gltf/extensions/physics/gltf_physics_shape.cpp index d3c56c0da9..af4ac10313 100644 --- a/modules/gltf/extensions/physics/gltf_physics_shape.cpp +++ b/modules/gltf/extensions/physics/gltf_physics_shape.cpp @@ -129,16 +129,16 @@ void GLTFPhysicsShape::set_importer_mesh(Ref<ImporterMesh> p_importer_mesh) { importer_mesh = p_importer_mesh; } -Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_node(const CollisionShape3D *p_collider_node) { +Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_node(const CollisionShape3D *p_godot_shape_node) { Ref<GLTFPhysicsShape> gltf_shape; gltf_shape.instantiate(); - ERR_FAIL_NULL_V_MSG(p_collider_node, gltf_shape, "Tried to create a GLTFPhysicsShape from a CollisionShape3D node, but the given node was null."); - Node *parent = p_collider_node->get_parent(); + ERR_FAIL_NULL_V_MSG(p_godot_shape_node, gltf_shape, "Tried to create a GLTFPhysicsShape from a CollisionShape3D node, but the given node was null."); + Node *parent = p_godot_shape_node->get_parent(); if (cast_to<const Area3D>(parent)) { gltf_shape->set_is_trigger(true); } // All the code for working with the shape is below this comment. - Ref<Shape3D> shape_resource = p_collider_node->get_shape(); + Ref<Shape3D> shape_resource = p_godot_shape_node->get_shape(); ERR_FAIL_COND_V_MSG(shape_resource.is_null(), gltf_shape, "Tried to create a GLTFPhysicsShape from a CollisionShape3D node, but the given node had a null shape."); gltf_shape->_shape_cache = shape_resource; if (cast_to<BoxShape3D>(shape_resource.ptr())) { @@ -160,7 +160,7 @@ Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_node(const CollisionShape3D *p_coll Ref<SphereShape3D> sphere = shape_resource; gltf_shape->set_radius(sphere->get_radius()); } else if (cast_to<const ConvexPolygonShape3D>(shape_resource.ptr())) { - gltf_shape->shape_type = "hull"; + gltf_shape->shape_type = "convex"; Ref<ConvexPolygonShape3D> convex = shape_resource; Vector<Vector3> hull_points = convex->get_points(); ERR_FAIL_COND_V_MSG(hull_points.size() < 3, gltf_shape, "GLTFPhysicsShape: Convex hull has fewer points (" + itos(hull_points.size()) + ") than the minimum of 3. At least 3 points are required in order to save to GLTF, since it uses a mesh to represent convex hulls."); @@ -206,7 +206,7 @@ Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_node(const CollisionShape3D *p_coll } CollisionShape3D *GLTFPhysicsShape::to_node(bool p_cache_shapes) { - CollisionShape3D *gltf_shape = memnew(CollisionShape3D); + CollisionShape3D *godot_shape_node = memnew(CollisionShape3D); if (!p_cache_shapes || _shape_cache == nullptr) { if (shape_type == "box") { Ref<BoxShape3D> box; @@ -230,80 +230,88 @@ CollisionShape3D *GLTFPhysicsShape::to_node(bool p_cache_shapes) { sphere.instantiate(); sphere->set_radius(radius); _shape_cache = sphere; - } else if (shape_type == "hull") { - ERR_FAIL_COND_V_MSG(importer_mesh.is_null(), gltf_shape, "GLTFPhysicsShape: Error converting convex hull shape to a node: The mesh resource is null."); + } else if (shape_type == "convex") { + ERR_FAIL_COND_V_MSG(importer_mesh.is_null(), godot_shape_node, "GLTFPhysicsShape: Error converting convex hull shape to a node: The mesh resource is null."); Ref<ConvexPolygonShape3D> convex = importer_mesh->get_mesh()->create_convex_shape(); _shape_cache = convex; } else if (shape_type == "trimesh") { - ERR_FAIL_COND_V_MSG(importer_mesh.is_null(), gltf_shape, "GLTFPhysicsShape: Error converting concave mesh shape to a node: The mesh resource is null."); + ERR_FAIL_COND_V_MSG(importer_mesh.is_null(), godot_shape_node, "GLTFPhysicsShape: Error converting concave mesh shape to a node: The mesh resource is null."); Ref<ConcavePolygonShape3D> concave = importer_mesh->create_trimesh_shape(); _shape_cache = concave; } else { ERR_PRINT("GLTFPhysicsShape: Error converting to a node: Shape type '" + shape_type + "' is unknown."); } } - gltf_shape->set_shape(_shape_cache); - return gltf_shape; + godot_shape_node->set_shape(_shape_cache); + return godot_shape_node; } Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_dictionary(const Dictionary p_dictionary) { ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFPhysicsShape>(), "Failed to parse GLTFPhysicsShape, missing required field 'type'."); Ref<GLTFPhysicsShape> gltf_shape; gltf_shape.instantiate(); - const String &shape_type = p_dictionary["type"]; + String shape_type = p_dictionary["type"]; + if (shape_type == "hull") { + shape_type = "convex"; + } gltf_shape->shape_type = shape_type; - if (shape_type != "box" && shape_type != "capsule" && shape_type != "cylinder" && shape_type != "sphere" && shape_type != "hull" && shape_type != "trimesh") { - ERR_PRINT("GLTFPhysicsShape: Error parsing unknown shape type '" + shape_type + "'. Only box, capsule, cylinder, sphere, hull, and trimesh are supported."); + if (shape_type != "box" && shape_type != "capsule" && shape_type != "cylinder" && shape_type != "sphere" && shape_type != "convex" && shape_type != "trimesh") { + ERR_PRINT("GLTFPhysicsShape: Error parsing unknown shape type '" + shape_type + "'. Only box, capsule, cylinder, sphere, convex, and trimesh are supported."); + } + Dictionary properties; + if (p_dictionary.has(shape_type)) { + properties = p_dictionary[shape_type]; + } else { + properties = p_dictionary; } - if (p_dictionary.has("radius")) { - gltf_shape->set_radius(p_dictionary["radius"]); + if (properties.has("radius")) { + gltf_shape->set_radius(properties["radius"]); } - if (p_dictionary.has("height")) { - gltf_shape->set_height(p_dictionary["height"]); + if (properties.has("height")) { + gltf_shape->set_height(properties["height"]); } - if (p_dictionary.has("size")) { - const Array &arr = p_dictionary["size"]; + if (properties.has("size")) { + const Array &arr = properties["size"]; if (arr.size() == 3) { gltf_shape->set_size(Vector3(arr[0], arr[1], arr[2])); } else { ERR_PRINT("GLTFPhysicsShape: Error parsing the size, it must have exactly 3 numbers."); } } - if (p_dictionary.has("isTrigger")) { - gltf_shape->set_is_trigger(p_dictionary["isTrigger"]); + if (properties.has("isTrigger")) { + gltf_shape->set_is_trigger(properties["isTrigger"]); } - if (p_dictionary.has("mesh")) { - gltf_shape->set_mesh_index(p_dictionary["mesh"]); + if (properties.has("mesh")) { + gltf_shape->set_mesh_index(properties["mesh"]); } - if (unlikely(gltf_shape->get_mesh_index() < 0 && (shape_type == "hull" || shape_type == "trimesh"))) { + if (unlikely(gltf_shape->get_mesh_index() < 0 && (shape_type == "convex" || shape_type == "trimesh"))) { ERR_PRINT("Error parsing GLTFPhysicsShape: The mesh-based shape type '" + shape_type + "' does not have a valid mesh index."); } return gltf_shape; } Dictionary GLTFPhysicsShape::to_dictionary() const { - Dictionary d; - d["type"] = shape_type; + Dictionary gltf_shape; + gltf_shape["type"] = shape_type; + Dictionary sub; if (shape_type == "box") { Array size_array; size_array.resize(3); size_array[0] = size.x; size_array[1] = size.y; size_array[2] = size.z; - d["size"] = size_array; + sub["size"] = size_array; } else if (shape_type == "capsule") { - d["radius"] = get_radius(); - d["height"] = get_height(); + sub["radius"] = get_radius(); + sub["height"] = get_height(); } else if (shape_type == "cylinder") { - d["radius"] = get_radius(); - d["height"] = get_height(); + sub["radius"] = get_radius(); + sub["height"] = get_height(); } else if (shape_type == "sphere") { - d["radius"] = get_radius(); - } else if (shape_type == "trimesh" || shape_type == "hull") { - d["mesh"] = get_mesh_index(); - } - if (is_trigger) { - d["isTrigger"] = is_trigger; + sub["radius"] = get_radius(); + } else if (shape_type == "trimesh" || shape_type == "convex") { + sub["mesh"] = get_mesh_index(); } - return d; + gltf_shape[shape_type] = sub; + return gltf_shape; } diff --git a/modules/gltf/extensions/physics/gltf_physics_shape.h b/modules/gltf/extensions/physics/gltf_physics_shape.h index efecf27e1b..4f7ac39292 100644 --- a/modules/gltf/extensions/physics/gltf_physics_shape.h +++ b/modules/gltf/extensions/physics/gltf_physics_shape.h @@ -37,8 +37,9 @@ class ImporterMesh; -// GLTFPhysicsShape is an intermediary between OMI_collider and Godot's collision shape nodes. -// https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_collider +// GLTFPhysicsShape is an intermediary between Godot's collision shape nodes +// and the OMI_physics_shape extension. +// https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_shape class GLTFPhysicsShape : public Resource { GDCLASS(GLTFPhysicsShape, Resource) diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index 4746ffb79b..d2fe8a7534 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -1711,7 +1711,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d push_constant.ray_from = i * max_rays; push_constant.ray_to = MIN((i + 1) * max_rays, int32_t(push_constant.ray_count)); rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant)); - rd->compute_list_dispatch(compute_list, Math::division_round_up(probe_positions.size(), 64), 1, 1); + rd->compute_list_dispatch(compute_list, Math::division_round_up((int)probe_positions.size(), 64), 1, 1); rd->compute_list_end(); //done rd->submit(); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs index d5d9404ed1..c806263edb 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs @@ -474,7 +474,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != IntPtr.Zero ? *((int*)_ptr - 1) : 0; + get => _ptr != IntPtr.Zero ? (int)(*((ulong*)_ptr - 1)) : 0; } } @@ -725,7 +725,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? *((int*)_ptr - 1) : 0; + get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; } } @@ -875,7 +875,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? *((int*)_ptr - 1) : 0; + get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; } } @@ -939,7 +939,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? *((int*)_ptr - 1) : 0; + get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; } } @@ -971,7 +971,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? *((int*)_ptr - 1) : 0; + get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; } } @@ -1003,7 +1003,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? *((int*)_ptr - 1) : 0; + get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; } } @@ -1035,7 +1035,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? *((int*)_ptr - 1) : 0; + get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; } } @@ -1067,7 +1067,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? *((int*)_ptr - 1) : 0; + get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; } } @@ -1099,7 +1099,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? *((int*)_ptr - 1) : 0; + get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; } } @@ -1131,7 +1131,7 @@ namespace Godot.NativeInterop public readonly unsafe int Size { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _ptr != null ? *((int*)_ptr - 1) : 0; + get => _ptr != null ? (int)(*((ulong*)_ptr - 1)) : 0; } } diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub index 4cf810f905..285c58a96e 100644 --- a/modules/openxr/SCsub +++ b/modules/openxr/SCsub @@ -93,36 +93,13 @@ if env["builtin_openxr"]: module_obj = [] env_openxr.add_source_files(module_obj, "*.cpp") -env_openxr.add_source_files(module_obj, "action_map/*.cpp") -env_openxr.add_source_files(module_obj, "scene/*.cpp") +env.modules_sources += module_obj -# We're a little more targeted with our extensions -if env["platform"] == "android": - env_openxr.add_source_files(module_obj, "extensions/openxr_android_extension.cpp") -if env["vulkan"]: - env_openxr.add_source_files(module_obj, "extensions/openxr_vulkan_extension.cpp") -if env["opengl3"] and env["platform"] != "macos": - env_openxr.add_source_files(module_obj, "extensions/openxr_opengl_extension.cpp") - -env_openxr.add_source_files(module_obj, "extensions/openxr_palm_pose_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_composition_layer_depth_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_eye_gaze_interaction.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_htc_controller_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_htc_vive_tracker_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_huawei_controller_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_hand_tracking_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_fb_foveation_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_fb_update_swapchain_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_fb_passthrough_extension_wrapper.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_fb_display_refresh_rate_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_pico_controller_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_wmr_controller_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_ml2_controller_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_extension_wrapper_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_api_extension.cpp") -env_openxr.add_source_files(module_obj, "extensions/openxr_meta_controller_extension.cpp") +Export("env_openxr") -env.modules_sources += module_obj +SConscript("action_map/SCsub") +SConscript("extensions/SCsub") +SConscript("scene/SCsub") if env.editor_build: SConscript("editor/SCsub") diff --git a/modules/openxr/action_map/SCsub b/modules/openxr/action_map/SCsub new file mode 100644 index 0000000000..7a493011ec --- /dev/null +++ b/modules/openxr/action_map/SCsub @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +Import("env") +Import("env_openxr") + +module_obj = [] + +env_openxr.add_source_files(module_obj, "*.cpp") + +env.modules_sources += module_obj diff --git a/modules/openxr/extensions/SCsub b/modules/openxr/extensions/SCsub new file mode 100644 index 0000000000..1bd9cfaa22 --- /dev/null +++ b/modules/openxr/extensions/SCsub @@ -0,0 +1,18 @@ +#!/usr/bin/env python + +Import("env") +Import("env_openxr") + +module_obj = [] + +env_openxr.add_source_files(module_obj, "*.cpp") + +# These are platform dependent +if env["platform"] == "android": + env_openxr.add_source_files(module_obj, "platform/openxr_android_extension.cpp") +if env["vulkan"]: + env_openxr.add_source_files(module_obj, "platform/openxr_vulkan_extension.cpp") +if env["opengl3"] and env["platform"] != "macos": + env_openxr.add_source_files(module_obj, "platform/openxr_opengl_extension.cpp") + +env.modules_sources += module_obj diff --git a/modules/openxr/extensions/openxr_local_floor_extension.cpp b/modules/openxr/extensions/openxr_local_floor_extension.cpp new file mode 100644 index 0000000000..8e06dd8ed5 --- /dev/null +++ b/modules/openxr/extensions/openxr_local_floor_extension.cpp @@ -0,0 +1,59 @@ +/**************************************************************************/ +/* openxr_local_floor_extension.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "openxr_local_floor_extension.h" + +#include "core/string/print_string.h" + +OpenXRLocalFloorExtension *OpenXRLocalFloorExtension::singleton = nullptr; + +OpenXRLocalFloorExtension *OpenXRLocalFloorExtension::get_singleton() { + return singleton; +} + +OpenXRLocalFloorExtension::OpenXRLocalFloorExtension() { + singleton = this; +} + +OpenXRLocalFloorExtension::~OpenXRLocalFloorExtension() { + singleton = nullptr; +} + +HashMap<String, bool *> OpenXRLocalFloorExtension::get_requested_extensions() { + HashMap<String, bool *> request_extensions; + + request_extensions[XR_EXT_LOCAL_FLOOR_EXTENSION_NAME] = &available; + + return request_extensions; +} + +bool OpenXRLocalFloorExtension::is_available() { + return available; +} diff --git a/modules/openxr/extensions/openxr_local_floor_extension.h b/modules/openxr/extensions/openxr_local_floor_extension.h new file mode 100644 index 0000000000..dff97d9954 --- /dev/null +++ b/modules/openxr/extensions/openxr_local_floor_extension.h @@ -0,0 +1,53 @@ +/**************************************************************************/ +/* openxr_local_floor_extension.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef OPENXR_LOCAL_FLOOR_EXTENSION_H +#define OPENXR_LOCAL_FLOOR_EXTENSION_H + +#include "openxr_extension_wrapper.h" + +class OpenXRLocalFloorExtension : public OpenXRExtensionWrapper { +public: + static OpenXRLocalFloorExtension *get_singleton(); + + OpenXRLocalFloorExtension(); + virtual ~OpenXRLocalFloorExtension() override; + + virtual HashMap<String, bool *> get_requested_extensions() override; + + bool is_available(); + +private: + static OpenXRLocalFloorExtension *singleton; + + bool available = false; +}; + +#endif // OPENXR_LOCAL_FLOOR_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_android_extension.cpp b/modules/openxr/extensions/platform/openxr_android_extension.cpp index c6082ca404..de542828c3 100644 --- a/modules/openxr/extensions/openxr_android_extension.cpp +++ b/modules/openxr/extensions/platform/openxr_android_extension.cpp @@ -30,7 +30,7 @@ #include "openxr_android_extension.h" -#include "../openxr_api.h" +#include "../../openxr_api.h" #include "java_godot_wrapper.h" #include "os_android.h" diff --git a/modules/openxr/extensions/openxr_android_extension.h b/modules/openxr/extensions/platform/openxr_android_extension.h index 0e7c44d6d5..e51b5824e8 100644 --- a/modules/openxr/extensions/openxr_android_extension.h +++ b/modules/openxr/extensions/platform/openxr_android_extension.h @@ -31,8 +31,8 @@ #ifndef OPENXR_ANDROID_EXTENSION_H #define OPENXR_ANDROID_EXTENSION_H -#include "../util.h" -#include "openxr_extension_wrapper.h" +#include "../../util.h" +#include "../openxr_extension_wrapper.h" class OpenXRAndroidExtension : public OpenXRExtensionWrapper { public: diff --git a/modules/openxr/extensions/openxr_opengl_extension.cpp b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp index 9038e9f458..a9d970bbb9 100644 --- a/modules/openxr/extensions/openxr_opengl_extension.cpp +++ b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp @@ -32,7 +32,7 @@ #ifdef GLES3_ENABLED -#include "../openxr_util.h" +#include "../../openxr_util.h" #include "drivers/gles3/effects/copy_effects.h" #include "drivers/gles3/storage/texture_storage.h" diff --git a/modules/openxr/extensions/openxr_opengl_extension.h b/modules/openxr/extensions/platform/openxr_opengl_extension.h index 5f529829a7..a3052d3f53 100644 --- a/modules/openxr/extensions/openxr_opengl_extension.h +++ b/modules/openxr/extensions/platform/openxr_opengl_extension.h @@ -33,14 +33,14 @@ #ifdef GLES3_ENABLED -#include "../openxr_api.h" -#include "../util.h" -#include "openxr_extension_wrapper.h" +#include "../../openxr_api.h" +#include "../../util.h" +#include "../openxr_extension_wrapper.h" #include "core/templates/vector.h" // Always include this as late as possible. -#include "../openxr_platform_inc.h" +#include "../../openxr_platform_inc.h" class OpenXROpenGLExtension : public OpenXRGraphicsExtensionWrapper { public: diff --git a/modules/openxr/extensions/openxr_vulkan_extension.cpp b/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp index 9429d9e082..a2f2577959 100644 --- a/modules/openxr/extensions/openxr_vulkan_extension.cpp +++ b/modules/openxr/extensions/platform/openxr_vulkan_extension.cpp @@ -30,7 +30,7 @@ #include "openxr_vulkan_extension.h" -#include "../openxr_util.h" +#include "../../openxr_util.h" #include "core/string/print_string.h" #include "servers/rendering/renderer_rd/effects/copy_effects.h" diff --git a/modules/openxr/extensions/openxr_vulkan_extension.h b/modules/openxr/extensions/platform/openxr_vulkan_extension.h index 86c0f327dd..2d0973bb6b 100644 --- a/modules/openxr/extensions/openxr_vulkan_extension.h +++ b/modules/openxr/extensions/platform/openxr_vulkan_extension.h @@ -31,14 +31,14 @@ #ifndef OPENXR_VULKAN_EXTENSION_H #define OPENXR_VULKAN_EXTENSION_H -#include "../openxr_api.h" -#include "../util.h" -#include "openxr_extension_wrapper.h" +#include "../../openxr_api.h" +#include "../../util.h" +#include "../openxr_extension_wrapper.h" #include "core/templates/vector.h" // Always include this as late as possible. -#include "../openxr_platform_inc.h" +#include "../../openxr_platform_inc.h" class OpenXRVulkanExtension : public OpenXRGraphicsExtensionWrapper, VulkanHooks { public: diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index da6fd2e9b2..eafabe03e7 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -46,11 +46,11 @@ #include "openxr_platform_inc.h" #ifdef VULKAN_ENABLED -#include "extensions/openxr_vulkan_extension.h" +#include "extensions/platform/openxr_vulkan_extension.h" #endif #if defined(GLES3_ENABLED) && !defined(MACOS_ENABLED) -#include "extensions/openxr_opengl_extension.h" +#include "extensions/platform/openxr_opengl_extension.h" #endif #include "extensions/openxr_composition_layer_depth_extension.h" @@ -665,13 +665,6 @@ bool OpenXRAPI::load_supported_reference_spaces() { print_verbose(String("OpenXR: Found supported reference space ") + OpenXRUtil::get_reference_space_name(supported_reference_spaces[i])); } - // Check value we loaded at startup... - if (!is_reference_space_supported(reference_space)) { - print_verbose(String("OpenXR: ") + OpenXRUtil::get_reference_space_name(reference_space) + String(" isn't supported, defaulting to ") + OpenXRUtil::get_reference_space_name(supported_reference_spaces[0])); - - reference_space = supported_reference_spaces[0]; - } - return true; } @@ -699,16 +692,31 @@ bool OpenXRAPI::setup_spaces() { // create play space { - if (!is_reference_space_supported(reference_space)) { - print_line("OpenXR: reference space ", OpenXRUtil::get_reference_space_name(reference_space), " is not supported."); - return false; + emulating_local_floor = false; + + if (is_reference_space_supported(requested_reference_space)) { + reference_space = requested_reference_space; + } else if (requested_reference_space == XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT && is_reference_space_supported(XR_REFERENCE_SPACE_TYPE_STAGE)) { + print_verbose("OpenXR: LOCAL_FLOOR space isn't supported, emulating using STAGE and LOCAL spaces."); + + reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL; + emulating_local_floor = true; + + // We'll use the STAGE space to get the floor height, but we can't do that until + // after xrWaitFrame(), so just set this flag for now. + should_reset_emulated_floor_height = true; + + } else { + // Fallback on LOCAL, which all OpenXR runtimes are required to support. + print_verbose(String("OpenXR: ") + OpenXRUtil::get_reference_space_name(requested_reference_space) + String(" isn't supported, defaulting to LOCAL space.")); + reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL; } XrReferenceSpaceCreateInfo play_space_create_info = { XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type nullptr, // next reference_space, // referenceSpaceType - identityPose // poseInReferenceSpace + identityPose, // poseInReferenceSpace }; result = xrCreateReferenceSpace(session, &play_space_create_info, &play_space); @@ -742,6 +750,80 @@ bool OpenXRAPI::setup_spaces() { return true; } +bool OpenXRAPI::reset_emulated_floor_height() { + ERR_FAIL_COND_V(!emulating_local_floor, false); + + // This is based on the example code in the OpenXR spec which shows how to + // emulate LOCAL_FLOOR if it's not supported. + // See: https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_EXT_local_floor + + XrResult result; + + XrPosef identityPose = { + { 0.0, 0.0, 0.0, 1.0 }, + { 0.0, 0.0, 0.0 } + }; + + XrSpace local_space = XR_NULL_HANDLE; + XrSpace stage_space = XR_NULL_HANDLE; + + XrReferenceSpaceCreateInfo create_info = { + XR_TYPE_REFERENCE_SPACE_CREATE_INFO, // type + nullptr, // next + XR_REFERENCE_SPACE_TYPE_LOCAL, // referenceSpaceType + identityPose, // poseInReferenceSpace + }; + + result = xrCreateReferenceSpace(session, &create_info, &local_space); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create LOCAL space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]"); + return false; + } + + create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE; + result = xrCreateReferenceSpace(session, &create_info, &stage_space); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to create STAGE space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]"); + xrDestroySpace(local_space); + return false; + } + + XrSpaceLocation stage_location = { + XR_TYPE_SPACE_LOCATION, // type + nullptr, // next + 0, // locationFlags + identityPose, // pose + }; + + result = xrLocateSpace(stage_space, local_space, get_next_frame_time(), &stage_location); + + xrDestroySpace(local_space); + xrDestroySpace(stage_space); + + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to locate STAGE space in LOCAL space, in order to emulate LOCAL_FLOOR [", get_error_string(result), "]"); + return false; + } + + XrSpace new_play_space; + create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL; + create_info.poseInReferenceSpace.position.y = stage_location.pose.position.y; + result = xrCreateReferenceSpace(session, &create_info, &new_play_space); + if (XR_FAILED(result)) { + print_line("OpenXR: Failed to recreate emulated LOCAL_FLOOR play space with latest floor estimate [", get_error_string(result), "]"); + return false; + } + + xrDestroySpace(play_space); + play_space = new_play_space; + + // If we've made it this far, it means we can properly emulate LOCAL_FLOOR, so we'll + // report that as the reference space to the outside world. + reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT; + + return true; +} + bool OpenXRAPI::load_supported_swapchain_formats() { ERR_FAIL_COND_V(session == XR_NULL_HANDLE, false); @@ -1180,10 +1262,10 @@ void OpenXRAPI::set_view_configuration(XrViewConfigurationType p_view_configurat view_configuration = p_view_configuration; } -void OpenXRAPI::set_reference_space(XrReferenceSpaceType p_reference_space) { +void OpenXRAPI::set_requested_reference_space(XrReferenceSpaceType p_requested_reference_space) { ERR_FAIL_COND(is_initialized()); - reference_space = p_reference_space; + requested_reference_space = p_requested_reference_space; } void OpenXRAPI::set_submit_depth_buffer(bool p_submit_depth_buffer) { @@ -1628,6 +1710,9 @@ bool OpenXRAPI::poll_events() { XrEventDataReferenceSpaceChangePending *event = (XrEventDataReferenceSpaceChangePending *)&runtimeEvent; print_verbose(String("OpenXR EVENT: reference space type ") + OpenXRUtil::get_reference_space_name(event->referenceSpaceType) + " change pending!"); + if (emulating_local_floor) { + should_reset_emulated_floor_height = true; + } if (event->poseValid && xr_interface) { xr_interface->on_pose_recentered(); } @@ -1783,6 +1868,11 @@ void OpenXRAPI::pre_render() { frame_state.predictedDisplayPeriod = 0; } + if (unlikely(should_reset_emulated_floor_height)) { + reset_emulated_floor_height(); + should_reset_emulated_floor_height = false; + } + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_pre_render(); } @@ -2136,10 +2226,13 @@ OpenXRAPI::OpenXRAPI() { int reference_space_setting = GLOBAL_GET("xr/openxr/reference_space"); switch (reference_space_setting) { case 0: { - reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL; + requested_reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL; } break; case 1: { - reference_space = XR_REFERENCE_SPACE_TYPE_STAGE; + requested_reference_space = XR_REFERENCE_SPACE_TYPE_STAGE; + } break; + case 2: { + requested_reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT; } break; default: break; diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index efa32b7544..6e55020aef 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -98,7 +98,8 @@ private: // configuration XrFormFactor form_factor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY; XrViewConfigurationType view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; - XrReferenceSpaceType reference_space = XR_REFERENCE_SPACE_TYPE_STAGE; + XrReferenceSpaceType requested_reference_space = XR_REFERENCE_SPACE_TYPE_STAGE; + XrReferenceSpaceType reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL; bool submit_depth_buffer = false; // if set to true we submit depth buffers to OpenXR if a suitable extension is enabled. // blend mode @@ -149,6 +150,10 @@ private: bool view_pose_valid = false; XRPose::TrackingConfidence head_pose_confidence = XRPose::XR_TRACKING_CONFIDENCE_NONE; + bool emulating_local_floor = false; + bool should_reset_emulated_floor_height = false; + bool reset_emulated_floor_height(); + bool load_layer_properties(); bool load_supported_extensions(); bool is_extension_supported(const String &p_extension) const; @@ -333,7 +338,8 @@ public: void set_view_configuration(XrViewConfigurationType p_view_configuration); XrViewConfigurationType get_view_configuration() const { return view_configuration; } - void set_reference_space(XrReferenceSpaceType p_reference_space); + void set_requested_reference_space(XrReferenceSpaceType p_requested_reference_space); + XrReferenceSpaceType get_requested_reference_space() const { return requested_reference_space; } XrReferenceSpaceType get_reference_space() const { return reference_space; } void set_submit_depth_buffer(bool p_submit_depth_buffer); diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index ceeb1b0278..6b311b73a8 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -686,15 +686,48 @@ Dictionary OpenXRInterface::get_system_info() { } bool OpenXRInterface::supports_play_area_mode(XRInterface::PlayAreaMode p_mode) { - return false; + if (p_mode == XRInterface::XR_PLAY_AREA_3DOF) { + return false; + } + return true; } XRInterface::PlayAreaMode OpenXRInterface::get_play_area_mode() const { + if (!openxr_api || !initialized) { + return XRInterface::XR_PLAY_AREA_UNKNOWN; + } + + XrReferenceSpaceType reference_space = openxr_api->get_reference_space(); + + if (reference_space == XR_REFERENCE_SPACE_TYPE_LOCAL) { + return XRInterface::XR_PLAY_AREA_SITTING; + } else if (reference_space == XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT) { + return XRInterface::XR_PLAY_AREA_ROOMSCALE; + } else if (reference_space == XR_REFERENCE_SPACE_TYPE_STAGE) { + return XRInterface::XR_PLAY_AREA_STAGE; + } + return XRInterface::XR_PLAY_AREA_UNKNOWN; } bool OpenXRInterface::set_play_area_mode(XRInterface::PlayAreaMode p_mode) { - return false; + ERR_FAIL_COND_V_MSG(initialized, false, "Cannot change play area mode after OpenXR interface has been initialized"); + ERR_FAIL_NULL_V(openxr_api, false); + + XrReferenceSpaceType reference_space; + + if (p_mode == XRInterface::XR_PLAY_AREA_SITTING) { + reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL; + } else if (p_mode == XRInterface::XR_PLAY_AREA_ROOMSCALE) { + reference_space = XR_REFERENCE_SPACE_TYPE_LOCAL_FLOOR_EXT; + } else if (p_mode == XRInterface::XR_PLAY_AREA_STAGE) { + reference_space = XR_REFERENCE_SPACE_TYPE_STAGE; + } else { + return false; + } + + openxr_api->set_requested_reference_space(reference_space); + return true; } PackedVector3Array OpenXRInterface::get_play_area() const { diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp index 5cc793dca3..04411a0c57 100644 --- a/modules/openxr/register_types.cpp +++ b/modules/openxr/register_types.cpp @@ -49,6 +49,7 @@ #include "extensions/openxr_htc_controller_extension.h" #include "extensions/openxr_htc_vive_tracker_extension.h" #include "extensions/openxr_huawei_controller_extension.h" +#include "extensions/openxr_local_floor_extension.h" #include "extensions/openxr_meta_controller_extension.h" #include "extensions/openxr_ml2_controller_extension.h" #include "extensions/openxr_palm_pose_extension.h" @@ -60,7 +61,7 @@ #endif #ifdef ANDROID_ENABLED -#include "extensions/openxr_android_extension.h" +#include "extensions/platform/openxr_android_extension.h" #endif #include "core/config/project_settings.h" @@ -107,6 +108,7 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { // register our other extensions OpenXRAPI::register_extension_wrapper(memnew(OpenXRPalmPoseExtension)); + OpenXRAPI::register_extension_wrapper(memnew(OpenXRLocalFloorExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRPicoControllerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRCompositionLayerDepthExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRHTCControllerExtension)); diff --git a/modules/openxr/scene/SCsub b/modules/openxr/scene/SCsub new file mode 100644 index 0000000000..7a493011ec --- /dev/null +++ b/modules/openxr/scene/SCsub @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +Import("env") +Import("env_openxr") + +module_obj = [] + +env_openxr.add_source_files(module_obj, "*.cpp") + +env.modules_sources += module_obj |
