diff --git a/modules/gltf/doc_classes/GLTFPhysicsBody.xml b/modules/gltf/doc_classes/GLTFPhysicsBody.xml index cf39721ce8f..ca66cd54b04 100644 --- a/modules/gltf/doc_classes/GLTFPhysicsBody.xml +++ b/modules/gltf/doc_classes/GLTFPhysicsBody.xml @@ -4,7 +4,7 @@ Represents a GLTF physics body. - 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. $DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html @@ -15,7 +15,7 @@ - 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. @@ -28,7 +28,7 @@ - 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. @@ -42,13 +42,20 @@ The angular velocity of the physics body, in radians per second. This is only used when the body type is "rigid" or "vehicle". - - 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". + + 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. 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. - + + 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. + + + 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. + + 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. diff --git a/modules/gltf/doc_classes/GLTFPhysicsShape.xml b/modules/gltf/doc_classes/GLTFPhysicsShape.xml index 67382f32957..c397c660d91 100644 --- a/modules/gltf/doc_classes/GLTFPhysicsShape.xml +++ b/modules/gltf/doc_classes/GLTFPhysicsShape.xml @@ -4,11 +4,12 @@ Represents a GLTF physics shape. - 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. $DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html - https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_collider + https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_shape + https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/Archived/OMI_collider @@ -28,7 +29,7 @@ - Serializes this GLTFPhysicsShape instance into a [Dictionary]. + Serializes this GLTFPhysicsShape instance into a [Dictionary] in the format defined by [code]OMI_physics_shape[/code]. diff --git a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp index 2ba5123c31a..37b8ae06346 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 p_state, Vector 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 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 GLTFDocumentExtensionPhysics::get_supported_extensions() { Vector 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 p_state, Ref 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 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 p_state, Ref 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 p_state, Ref 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 importer_mesh = p_collider->get_importer_mesh(); + Ref importer_mesh = p_gltf_shape->get_importer_mesh(); if (importer_mesh.is_valid()) { return; // The mesh resource is already set up. } TypedArray 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 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 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 p_state, Ref p_gltf_node, Ref p_collider, Ref 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 p_state, Ref p_gltf_node, Ref p_physics_shape, Ref 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 p_state, Refto_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,35 +178,133 @@ CollisionObject3D *_generate_collision_with_body(Ref p_state, Refto_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 -Node3D *GLTFDocumentExtensionPhysics::generate_scene_node(Ref p_state, Ref p_gltf_node, Node *p_scene_parent) { - Ref physics_body = p_gltf_node->get_additional_data(StringName("GLTFPhysicsBody")); - Ref 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(p_scene_parent)) { - return collider->to_node(true); - } - } else { - if (Object::cast_to(p_scene_parent)) { - return collider->to_node(true); - } +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(p_scene_parent); + if (likely(co)) { + return co; } - return _generate_collision_with_body(p_state, p_gltf_node, collider, physics_body); - } - if (physics_body.is_valid()) { - return physics_body->to_node(); } return nullptr; } +Node3D *_generate_shape_node_and_body_if_needed(Ref p_state, Ref p_gltf_node, Ref 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(p_col_object)) { + body_node = memnew(Area3D); + } + } else { + if (!Object::cast_to(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 p_gltf_node) { + if (!p_current_node) { + return p_child; + } + String suffix; + if (Object::cast_to(p_child)) { + suffix = "Shape"; + } else if (Object::cast_to(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 p_state, Ref p_gltf_node, Node *p_scene_parent) { + Ref 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 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(ancestor_col_obj)) { + return gltf_physics_shape->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(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; + } + } + } + // 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 gltf_physics_collider_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape")); + Ref gltf_physics_trigger_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShape")); + bool is_ancestor_col_obj_solid = Object::cast_to(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); + } + 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. bool _are_all_faces_equal(const Vector &p_a, const Vector &p_b) { if (p_a.size() != p_b.size()) { @@ -202,22 +352,26 @@ GLTFMeshIndex _get_or_insert_mesh_in_state(Ref p_state, Ref p_state, Ref p_gltf_node, Node *p_scene_node) { if (cast_to(p_scene_node)) { - CollisionShape3D *shape = Object::cast_to(p_scene_node); - Ref collider = GLTFPhysicsShape::from_node(shape); + CollisionShape3D *godot_shape = Object::cast_to(p_scene_node); + Ref gltf_shape = GLTFPhysicsShape::from_node(godot_shape); { - Ref importer_mesh = collider->get_importer_mesh(); + Ref 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(_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(p_scene_node)) { - CollisionObject3D *body = Object::cast_to(p_scene_node); - p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), GLTFPhysicsBody::from_node(body)); + CollisionObject3D *godot_body = Object::cast_to(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 p_state) { +Array _get_or_create_state_shapes_in_state(Ref 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 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_colliders; + return state_shapes; +} + +Dictionary _export_node_shape(Ref p_state, Ref 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; + } + } + // 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 p_state, Ref p_gltf_node, Dictionary &r_node_json, Node *p_node) { - Dictionary node_extensions = r_node_json["extensions"]; + Dictionary physics_body_ext; Ref 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 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 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 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 b80f4348c26..271bb9b3329 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::from_node(const CollisionObject3D *p_body_node) { Ref 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(p_body_node)) { - physics_body->body_type = "character"; + physics_body->body_type = PhysicsBodyType::CHARACTER; } else if (cast_to(p_body_node)) { - physics_body->body_type = "kinematic"; + physics_body->body_type = PhysicsBodyType::ANIMATABLE; } else if (cast_to(p_body_node)) { const RigidBody3D *body = cast_to(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(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(p_body_node)) { - physics_body->body_type = "static"; + physics_body->body_type = PhysicsBodyType::STATIC; } else if (cast_to(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; + 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; + } } - 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; - } - 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::from_dictionary(const Dictionary p_dictionary) { Ref 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_tensor != Basis(0, 0, 0, 0, 0, 0, 0, 0, 0)) { + if (inertia_diagonal != Vector3()) { 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(3); + inertia_array[0] = inertia_diagonal[0]; + inertia_array[1] = inertia_diagonal[1]; + inertia_array[2] = inertia_diagonal[2]; + motion["inertiaDiagonal"] = inertia_array; } - return d; + if (inertia_orientation != Quaternion()) { + Array 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; + } + 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 391b4b873f6..6b21639a7b9 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 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 d3c56c0da93..af4ac10313f 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 p_importer_mesh) { importer_mesh = p_importer_mesh; } -Ref GLTFPhysicsShape::from_node(const CollisionShape3D *p_collider_node) { +Ref GLTFPhysicsShape::from_node(const CollisionShape3D *p_godot_shape_node) { Ref 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(parent)) { gltf_shape->set_is_trigger(true); } // All the code for working with the shape is below this comment. - Ref shape_resource = p_collider_node->get_shape(); + Ref 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(shape_resource.ptr())) { @@ -160,7 +160,7 @@ Ref GLTFPhysicsShape::from_node(const CollisionShape3D *p_coll Ref sphere = shape_resource; gltf_shape->set_radius(sphere->get_radius()); } else if (cast_to(shape_resource.ptr())) { - gltf_shape->shape_type = "hull"; + gltf_shape->shape_type = "convex"; Ref convex = shape_resource; Vector 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::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 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 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 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::from_dictionary(const Dictionary p_dictionary) { ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref(), "Failed to parse GLTFPhysicsShape, missing required field 'type'."); Ref 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."); } - if (p_dictionary.has("radius")) { - gltf_shape->set_radius(p_dictionary["radius"]); + Dictionary properties; + if (p_dictionary.has(shape_type)) { + properties = p_dictionary[shape_type]; + } else { + properties = p_dictionary; } - if (p_dictionary.has("height")) { - gltf_shape->set_height(p_dictionary["height"]); + if (properties.has("radius")) { + gltf_shape->set_radius(properties["radius"]); } - if (p_dictionary.has("size")) { - const Array &arr = p_dictionary["size"]; + if (properties.has("height")) { + gltf_shape->set_height(properties["height"]); + } + 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(); + sub["radius"] = get_radius(); + } else if (shape_type == "trimesh" || shape_type == "convex") { + sub["mesh"] = get_mesh_index(); } - if (is_trigger) { - d["isTrigger"] = is_trigger; - } - 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 efecf27e1b2..4f7ac392924 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)