GLTF: Update OMI_physics_body, add OMI_physics_shape, keep OMI_collider

This commit is contained in:
Aaron Franke 2023-08-11 11:16:40 -05:00
parent 0bcc0e92b3
commit b00e5cef63
No known key found for this signature in database
GPG key ID: 40A1750B977E56BF
7 changed files with 555 additions and 218 deletions

View file

@ -4,7 +4,7 @@
Represents a GLTF physics body. Represents a GLTF physics body.
</brief_description> </brief_description>
<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> </description>
<tutorials> <tutorials>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> <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" /> <return type="GLTFPhysicsBody" />
<param index="0" name="dictionary" type="Dictionary" /> <param index="0" name="dictionary" type="Dictionary" />
<description> <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> </description>
</method> </method>
<method name="from_node" qualifiers="static"> <method name="from_node" qualifiers="static">
@ -28,7 +28,7 @@
<method name="to_dictionary" qualifiers="const"> <method name="to_dictionary" qualifiers="const">
<return type="Dictionary" /> <return type="Dictionary" />
<description> <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> </description>
</method> </method>
<method name="to_node" qualifiers="const"> <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)"> <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". 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>
<member name="body_type" type="String" setter="set_body_type" getter="get_body_type" default="&quot;static&quot;"> <member name="body_type" type="String" setter="set_body_type" getter="get_body_type" default="&quot;rigid&quot;">
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.
</member> </member>
<member name="center_of_mass" type="Vector3" setter="set_center_of_mass" getter="get_center_of_mass" default="Vector3(0, 0, 0)"> <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. 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>
<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". 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. When converted to a Godot [RigidBody3D] node, if this value is zero, then the inertia will be calculated automatically.
</member> </member>

View file

@ -4,11 +4,12 @@
Represents a GLTF physics shape. Represents a GLTF physics shape.
</brief_description> </brief_description>
<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> </description>
<tutorials> <tutorials>
<link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> <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> </tutorials>
<methods> <methods>
<method name="from_dictionary" qualifiers="static"> <method name="from_dictionary" qualifiers="static">
@ -28,7 +29,7 @@
<method name="to_dictionary" qualifiers="const"> <method name="to_dictionary" qualifiers="const">
<return type="Dictionary" /> <return type="Dictionary" />
<description> <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> </description>
</method> </method>
<method name="to_node"> <method name="to_node">

View file

@ -34,13 +34,26 @@
// Import process. // Import process.
Error GLTFDocumentExtensionPhysics::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) { 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; return ERR_SKIP;
} }
Dictionary state_json = p_state->get_json(); Dictionary state_json = p_state->get_json();
if (state_json.has("extensions")) { if (state_json.has("extensions")) {
Dictionary state_extensions = state_json["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"]; Dictionary omi_collider_ext = state_extensions["OMI_collider"];
if (omi_collider_ext.has("colliders")) { if (omi_collider_ext.has("colliders")) {
Array state_collider_dicts = omi_collider_ext["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++) { for (int i = 0; i < state_collider_dicts.size(); i++) {
state_colliders.push_back(GLTFPhysicsShape::from_dictionary(state_collider_dicts[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; return OK;
@ -61,49 +75,87 @@ Vector<String> GLTFDocumentExtensionPhysics::get_supported_extensions() {
Vector<String> ret; Vector<String> ret;
ret.push_back("OMI_collider"); ret.push_back("OMI_collider");
ret.push_back("OMI_physics_body"); ret.push_back("OMI_physics_body");
ret.push_back("OMI_physics_shape");
return ret; return ret;
} }
Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions) { 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")) { if (p_extensions.has("OMI_collider")) {
Dictionary node_collider_ext = p_extensions["OMI_collider"]; Dictionary node_collider_ext = p_extensions["OMI_collider"];
if (node_collider_ext.has("collider")) { if (node_collider_ext.has("collider")) {
// "collider" is the index of the collider in the state colliders array. // "collider" is the index of the collider in the state colliders array.
int node_collider_index = node_collider_ext["collider"]; 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()) + ")."); 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]); p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape"), state_colliders[node_collider_index]);
} else { } 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")) { 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; return OK;
} }
void _setup_collider_mesh_resource_from_index_if_needed(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_collider) { void _setup_shape_mesh_resource_from_index_if_needed(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_gltf_shape) {
GLTFMeshIndex collider_mesh_index = p_collider->get_mesh_index(); GLTFMeshIndex shape_mesh_index = p_gltf_shape->get_mesh_index();
if (collider_mesh_index == -1) { if (shape_mesh_index == -1) {
return; // No mesh for this collider. 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()) { if (importer_mesh.is_valid()) {
return; // The mesh resource is already set up. return; // The mesh resource is already set up.
} }
TypedArray<GLTFMesh> state_meshes = p_state->get_meshes(); 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()) + ")."); 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[collider_mesh_index]; Ref<GLTFMesh> gltf_mesh = state_meshes[shape_mesh_index];
ERR_FAIL_COND(gltf_mesh.is_null()); ERR_FAIL_COND(gltf_mesh.is_null());
importer_mesh = gltf_mesh->get_mesh(); importer_mesh = gltf_mesh->get_mesh();
ERR_FAIL_COND(importer_mesh.is_null()); 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) { #ifndef DISABLE_DEPRECATED
print_verbose("glTF: Creating collision for: " + p_gltf_node->get_name()); CollisionObject3D *_generate_shape_with_body(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Ref<GLTFPhysicsShape> p_physics_shape, Ref<GLTFPhysicsBody> p_physics_body) {
bool is_trigger = p_collider->get_is_trigger(); 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 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 // 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 // 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()) { if (p_physics_body.is_valid()) {
// This code is run when the physics body is on the same GLTF node. // This code is run when the physics body is on the same GLTF node.
body = p_physics_body->to_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 // Edge case: If the body's trigger and the collider's trigger
// are in disagreement, we need to create another new body. // 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"))); child->set_name(p_gltf_node->get_name() + (is_trigger ? String("Trigger") : String("Solid")));
body->add_child(child); body->add_child(child);
return body; return body;
@ -126,35 +178,133 @@ CollisionObject3D *_generate_collision_with_body(Ref<GLTFState> p_state, Ref<GLT
} else { } else {
body = memnew(StaticBody3D); 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"); shape->set_name(p_gltf_node->get_name() + "Shape");
body->add_child(shape); body->add_child(shape);
return body; return body;
} }
#endif // DISABLE_DEPRECATED
Node3D *GLTFDocumentExtensionPhysics::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) { CollisionObject3D *_get_ancestor_collision_object(Node *p_scene_parent) {
Ref<GLTFPhysicsBody> physics_body = p_gltf_node->get_additional_data(StringName("GLTFPhysicsBody")); // Note: Despite the name of the method, at the moment this only checks
Ref<GLTFPhysicsShape> collider = p_gltf_node->get_additional_data(StringName("GLTFPhysicsShape")); // the direct parent. Only check more later if Godot adds support for it.
if (collider.is_valid()) { if (p_scene_parent) {
_setup_collider_mesh_resource_from_index_if_needed(p_state, collider); CollisionObject3D *co = Object::cast_to<CollisionObject3D>(p_scene_parent);
// If the collider has the correct type of parent, we just return one node. if (likely(co)) {
if (collider->get_is_trigger()) { return co;
if (Object::cast_to<Area3D>(p_scene_parent)) {
return collider->to_node(true);
}
} else {
if (Object::cast_to<PhysicsBody3D>(p_scene_parent)) {
return collider->to_node(true);
}
} }
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; 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> 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 (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;
}
}
}
// 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);
}
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. // Export process.
bool _are_all_faces_equal(const Vector<Face3> &p_a, const Vector<Face3> &p_b) { bool _are_all_faces_equal(const Vector<Face3> &p_a, const Vector<Face3> &p_b) {
if (p_a.size() != p_b.size()) { if (p_a.size() != p_b.size()) {
@ -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) { 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)) { if (cast_to<CollisionShape3D>(p_scene_node)) {
CollisionShape3D *shape = Object::cast_to<CollisionShape3D>(p_scene_node); CollisionShape3D *godot_shape = Object::cast_to<CollisionShape3D>(p_scene_node);
Ref<GLTFPhysicsShape> collider = GLTFPhysicsShape::from_node(shape); 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()) { 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)) { } else if (cast_to<CollisionObject3D>(p_scene_node)) {
CollisionObject3D *body = Object::cast_to<CollisionObject3D>(p_scene_node); CollisionObject3D *godot_body = Object::cast_to<CollisionObject3D>(p_scene_node);
p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), GLTFPhysicsBody::from_node(body)); 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_json = p_state->get_json();
Dictionary state_extensions; Dictionary state_extensions;
if (state_json.has("extensions")) { if (state_json.has("extensions")) {
@ -225,48 +379,60 @@ Array _get_or_create_state_colliders_in_state(Ref<GLTFState> p_state) {
} else { } else {
state_json["extensions"] = state_extensions; state_json["extensions"] = state_extensions;
} }
Dictionary omi_collider_ext; Dictionary omi_physics_shape_ext;
if (state_extensions.has("OMI_collider")) { if (state_extensions.has("OMI_physics_shape")) {
omi_collider_ext = state_extensions["OMI_collider"]; omi_physics_shape_ext = state_extensions["OMI_physics_shape"];
} else { } else {
state_extensions["OMI_collider"] = omi_collider_ext; state_extensions["OMI_physics_shape"] = omi_physics_shape_ext;
p_state->add_used_extension("OMI_collider"); p_state->add_used_extension("OMI_physics_shape");
} }
Array state_colliders; Array state_shapes;
if (omi_collider_ext.has("colliders")) { if (omi_physics_shape_ext.has("shapes")) {
state_colliders = omi_collider_ext["colliders"]; state_shapes = omi_physics_shape_ext["shapes"];
} else { } 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<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;
}
}
// 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) { 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")); Ref<GLTFPhysicsBody> physics_body = p_gltf_node->get_additional_data(StringName("GLTFPhysicsBody"));
if (physics_body.is_valid()) { if (physics_body.is_valid()) {
node_extensions["OMI_physics_body"] = physics_body->to_dictionary(); physics_body_ext = physics_body->to_dictionary();
p_state->add_used_extension("OMI_physics_body");
} }
Ref<GLTFPhysicsShape> collider = p_gltf_node->get_additional_data(StringName("GLTFPhysicsShape")); Ref<GLTFPhysicsShape> collider_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsColliderShape"));
if (collider.is_valid()) { if (collider_shape.is_valid()) {
Array state_colliders = _get_or_create_state_colliders_in_state(p_state); physics_body_ext["collider"] = _export_node_shape(p_state, collider_shape);
int size = state_colliders.size(); }
Dictionary omi_collider_ext; Ref<GLTFPhysicsShape> trigger_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsTriggerShape"));
node_extensions["OMI_collider"] = omi_collider_ext; if (trigger_shape.is_valid()) {
Dictionary collider_dict = collider->to_dictionary(); physics_body_ext["trigger"] = _export_node_shape(p_state, trigger_shape);
for (int i = 0; i < size; i++) { }
Dictionary other = state_colliders[i]; if (!physics_body_ext.is_empty()) {
if (other == collider_dict) { Dictionary node_extensions = r_node_json["extensions"];
// De-duplication: If we already have an identical collider, node_extensions["OMI_physics_body"] = physics_body_ext;
// set the collider index to the existing one and return. p_state->add_used_extension("OMI_physics_body");
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;
} }
return OK; return OK;
} }

View file

@ -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("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("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("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("get_inertia_tensor"), &GLTFPhysicsBody::get_inertia_tensor);
ClassDB::bind_method(D_METHOD("set_inertia_tensor", "inertia_tensor"), &GLTFPhysicsBody::set_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::STRING, "body_type"), "set_body_type", "get_body_type");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass"), "set_mass", "get_mass"); 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, "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, "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, "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"); ADD_PROPERTY(PropertyInfo(Variant::BASIS, "inertia_tensor"), "set_inertia_tensor", "get_inertia_tensor");
#endif // DISABLE_DEPRECATED
} }
String GLTFPhysicsBody::get_body_type() const { 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) { 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; 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; 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 { Basis GLTFPhysicsBody::get_inertia_tensor() const {
return inertia_tensor; return Basis::from_scale(inertia_diagonal);
} }
void GLTFPhysicsBody::set_inertia_tensor(Basis p_inertia_tensor) { 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> GLTFPhysicsBody::from_node(const CollisionObject3D *p_body_node) {
Ref<GLTFPhysicsBody> physics_body; Ref<GLTFPhysicsBody> physics_body;
physics_body.instantiate(); 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."); 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)) { 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)) { } 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)) { } else if (cast_to<RigidBody3D>(p_body_node)) {
const RigidBody3D *body = cast_to<const RigidBody3D>(p_body_node); const RigidBody3D *body = cast_to<const RigidBody3D>(p_body_node);
physics_body->mass = body->get_mass(); physics_body->mass = body->get_mass();
physics_body->linear_velocity = body->get_linear_velocity(); physics_body->linear_velocity = body->get_linear_velocity();
physics_body->angular_velocity = body->get_angular_velocity(); physics_body->angular_velocity = body->get_angular_velocity();
physics_body->center_of_mass = body->get_center_of_mass(); physics_body->center_of_mass = body->get_center_of_mass();
Vector3 inertia_diagonal = body->get_inertia(); physics_body->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);
if (body->get_center_of_mass() != Vector3()) { 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."); 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)) { if (cast_to<VehicleBody3D>(p_body_node)) {
physics_body->body_type = "vehicle"; physics_body->body_type = PhysicsBodyType::VEHICLE;
} else { } else {
physics_body->body_type = "rigid"; physics_body->body_type = PhysicsBodyType::RIGID;
} }
} else if (cast_to<StaticBody3D>(p_body_node)) { } 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)) { } else if (cast_to<Area3D>(p_body_node)) {
physics_body->body_type = "trigger"; physics_body->body_type = PhysicsBodyType::TRIGGER;
} }
return physics_body; return physics_body;
} }
CollisionObject3D *GLTFPhysicsBody::to_node() const { CollisionObject3D *GLTFPhysicsBody::to_node() const {
if (body_type == "character") { switch (body_type) {
CharacterBody3D *body = memnew(CharacterBody3D); case PhysicsBodyType::CHARACTER: {
return body; 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") { // Unreachable, the switch cases handle all values the enum can take.
AnimatableBody3D *body = memnew(AnimatableBody3D); // Omitting this works on Clang but not GCC or MSVC. If reached, it's UB.
return body; return nullptr;
}
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.");
} }
Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_dictionary) { Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_dictionary) {
Ref<GLTFPhysicsBody> physics_body; Ref<GLTFPhysicsBody> physics_body;
physics_body.instantiate(); physics_body.instantiate();
ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), physics_body, "Failed to parse GLTF physics body, missing required field 'type'."); Dictionary motion;
const String &body_type = p_dictionary["type"]; if (p_dictionary.has("motion")) {
physics_body->body_type = body_type; motion = p_dictionary["motion"];
#ifndef DISABLE_DEPRECATED
if (p_dictionary.has("mass")) { } else {
physics_body->mass = p_dictionary["mass"]; motion = p_dictionary;
#endif // DISABLE_DEPRECATED
} }
if (p_dictionary.has("linearVelocity")) { if (motion.has("type")) {
const Array &arr = p_dictionary["linearVelocity"]; // 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) { if (arr.size() == 3) {
physics_body->set_linear_velocity(Vector3(arr[0], arr[1], arr[2])); physics_body->set_linear_velocity(Vector3(arr[0], arr[1], arr[2]));
} else { } else {
ERR_PRINT("Error parsing GLTF physics body: The linear velocity vector must have exactly 3 numbers."); ERR_PRINT("Error parsing GLTF physics body: The linear velocity vector must have exactly 3 numbers.");
} }
} }
if (p_dictionary.has("angularVelocity")) { if (motion.has("angularVelocity")) {
const Array &arr = p_dictionary["angularVelocity"]; const Array &arr = motion["angularVelocity"];
if (arr.size() == 3) { if (arr.size() == 3) {
physics_body->set_angular_velocity(Vector3(arr[0], arr[1], arr[2])); physics_body->set_angular_velocity(Vector3(arr[0], arr[1], arr[2]));
} else { } else {
ERR_PRINT("Error parsing GLTF physics body: The angular velocity vector must have exactly 3 numbers."); ERR_PRINT("Error parsing GLTF physics body: The angular velocity vector must have exactly 3 numbers.");
} }
} }
if (p_dictionary.has("centerOfMass")) { if (motion.has("centerOfMass")) {
const Array &arr = p_dictionary["centerOfMass"]; const Array &arr = motion["centerOfMass"];
if (arr.size() == 3) { if (arr.size() == 3) {
physics_body->set_center_of_mass(Vector3(arr[0], arr[1], arr[2])); physics_body->set_center_of_mass(Vector3(arr[0], arr[1], arr[2]));
} else { } else {
ERR_PRINT("Error parsing GLTF physics body: The center of mass vector must have exactly 3 numbers."); ERR_PRINT("Error parsing GLTF physics body: The center of mass vector must have exactly 3 numbers.");
} }
} }
if (p_dictionary.has("inertiaTensor")) { if (motion.has("inertiaDiagonal")) {
const Array &arr = p_dictionary["inertiaTensor"]; const Array &arr = motion["inertiaDiagonal"];
if (arr.size() == 9) { if (arr.size() == 3) {
// Only use the diagonal elements of the inertia tensor matrix (principal axes). physics_body->set_inertia_diagonal(Vector3(arr[0], arr[1], arr[2]));
physics_body->set_inertia_tensor(Basis(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6], arr[7], arr[8]));
} else { } 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") { if (motion.has("inertiaOrientation")) {
ERR_PRINT("Error parsing GLTF physics body: Body type '" + body_type + "' is unknown."); 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; return physics_body;
} }
Dictionary GLTFPhysicsBody::to_dictionary() const { Dictionary GLTFPhysicsBody::to_dictionary() const {
Dictionary d; Dictionary ret;
d["type"] = body_type; 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) { if (mass != 1.0) {
d["mass"] = mass; motion["mass"] = mass;
} }
if (linear_velocity != Vector3()) { if (linear_velocity != Vector3()) {
Array velocity_array; Array velocity_array;
@ -242,7 +365,7 @@ Dictionary GLTFPhysicsBody::to_dictionary() const {
velocity_array[0] = linear_velocity.x; velocity_array[0] = linear_velocity.x;
velocity_array[1] = linear_velocity.y; velocity_array[1] = linear_velocity.y;
velocity_array[2] = linear_velocity.z; velocity_array[2] = linear_velocity.z;
d["linearVelocity"] = velocity_array; motion["linearVelocity"] = velocity_array;
} }
if (angular_velocity != Vector3()) { if (angular_velocity != Vector3()) {
Array velocity_array; Array velocity_array;
@ -250,7 +373,7 @@ Dictionary GLTFPhysicsBody::to_dictionary() const {
velocity_array[0] = angular_velocity.x; velocity_array[0] = angular_velocity.x;
velocity_array[1] = angular_velocity.y; velocity_array[1] = angular_velocity.y;
velocity_array[2] = angular_velocity.z; velocity_array[2] = angular_velocity.z;
d["angularVelocity"] = velocity_array; motion["angularVelocity"] = velocity_array;
} }
if (center_of_mass != Vector3()) { if (center_of_mass != Vector3()) {
Array center_of_mass_array; 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[0] = center_of_mass.x;
center_of_mass_array[1] = center_of_mass.y; center_of_mass_array[1] = center_of_mass.y;
center_of_mass_array[2] = center_of_mass.z; 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; Array inertia_array;
inertia_array.resize(9); inertia_array.resize(3);
inertia_array.fill(0.0); inertia_array[0] = inertia_diagonal[0];
inertia_array[0] = inertia_tensor[0][0]; inertia_array[1] = inertia_diagonal[1];
inertia_array[1] = inertia_tensor[0][1]; inertia_array[2] = inertia_diagonal[2];
inertia_array[2] = inertia_tensor[0][2]; motion["inertiaDiagonal"] = inertia_array;
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;
} }
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;
} }

View file

@ -33,27 +33,47 @@
#include "scene/3d/physics_body_3d.h" #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 // https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_body
class GLTFPhysicsBody : public Resource { class GLTFPhysicsBody : public Resource {
GDCLASS(GLTFPhysicsBody, 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: protected:
static void _bind_methods(); static void _bind_methods();
private: private:
String body_type = "static"; PhysicsBodyType body_type = PhysicsBodyType::RIGID;
real_t mass = 1.0; real_t mass = 1.0;
Vector3 linear_velocity; Vector3 linear_velocity;
Vector3 angular_velocity; Vector3 angular_velocity;
Vector3 center_of_mass; Vector3 center_of_mass;
Basis inertia_tensor = Basis(0, 0, 0, 0, 0, 0, 0, 0, 0); Vector3 inertia_diagonal;
Quaternion inertia_orientation;
public: public:
String get_body_type() const; String get_body_type() const;
void set_body_type(String p_body_type); 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; real_t get_mass() const;
void set_mass(real_t p_mass); void set_mass(real_t p_mass);
@ -66,8 +86,16 @@ public:
Vector3 get_center_of_mass() const; Vector3 get_center_of_mass() const;
void set_center_of_mass(const Vector3 &p_center_of_mass); 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; Basis get_inertia_tensor() const;
void set_inertia_tensor(Basis p_inertia_tensor); void set_inertia_tensor(Basis p_inertia_tensor);
#endif // DISABLE_DEPRECATED
static Ref<GLTFPhysicsBody> from_node(const CollisionObject3D *p_body_node); static Ref<GLTFPhysicsBody> from_node(const CollisionObject3D *p_body_node);
CollisionObject3D *to_node() const; CollisionObject3D *to_node() const;

View file

@ -129,16 +129,16 @@ void GLTFPhysicsShape::set_importer_mesh(Ref<ImporterMesh> p_importer_mesh) {
importer_mesh = 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; Ref<GLTFPhysicsShape> gltf_shape;
gltf_shape.instantiate(); 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."); 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_collider_node->get_parent(); Node *parent = p_godot_shape_node->get_parent();
if (cast_to<const Area3D>(parent)) { if (cast_to<const Area3D>(parent)) {
gltf_shape->set_is_trigger(true); gltf_shape->set_is_trigger(true);
} }
// All the code for working with the shape is below this comment. // 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."); 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; gltf_shape->_shape_cache = shape_resource;
if (cast_to<BoxShape3D>(shape_resource.ptr())) { 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; Ref<SphereShape3D> sphere = shape_resource;
gltf_shape->set_radius(sphere->get_radius()); gltf_shape->set_radius(sphere->get_radius());
} else if (cast_to<const ConvexPolygonShape3D>(shape_resource.ptr())) { } else if (cast_to<const ConvexPolygonShape3D>(shape_resource.ptr())) {
gltf_shape->shape_type = "hull"; gltf_shape->shape_type = "convex";
Ref<ConvexPolygonShape3D> convex = shape_resource; Ref<ConvexPolygonShape3D> convex = shape_resource;
Vector<Vector3> hull_points = convex->get_points(); 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."); 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 *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 (!p_cache_shapes || _shape_cache == nullptr) {
if (shape_type == "box") { if (shape_type == "box") {
Ref<BoxShape3D> box; Ref<BoxShape3D> box;
@ -230,80 +230,88 @@ CollisionShape3D *GLTFPhysicsShape::to_node(bool p_cache_shapes) {
sphere.instantiate(); sphere.instantiate();
sphere->set_radius(radius); sphere->set_radius(radius);
_shape_cache = sphere; _shape_cache = sphere;
} else if (shape_type == "hull") { } else if (shape_type == "convex") {
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."); 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(); Ref<ConvexPolygonShape3D> convex = importer_mesh->get_mesh()->create_convex_shape();
_shape_cache = convex; _shape_cache = convex;
} else if (shape_type == "trimesh") { } 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(); Ref<ConcavePolygonShape3D> concave = importer_mesh->create_trimesh_shape();
_shape_cache = concave; _shape_cache = concave;
} else { } else {
ERR_PRINT("GLTFPhysicsShape: Error converting to a node: Shape type '" + shape_type + "' is unknown."); ERR_PRINT("GLTFPhysicsShape: Error converting to a node: Shape type '" + shape_type + "' is unknown.");
} }
} }
gltf_shape->set_shape(_shape_cache); godot_shape_node->set_shape(_shape_cache);
return gltf_shape; return godot_shape_node;
} }
Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_dictionary(const Dictionary p_dictionary) { 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'."); ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFPhysicsShape>(), "Failed to parse GLTFPhysicsShape, missing required field 'type'.");
Ref<GLTFPhysicsShape> gltf_shape; Ref<GLTFPhysicsShape> gltf_shape;
gltf_shape.instantiate(); 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; 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") { 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, hull, and trimesh are supported."); 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")) { Dictionary properties;
gltf_shape->set_radius(p_dictionary["radius"]); if (p_dictionary.has(shape_type)) {
properties = p_dictionary[shape_type];
} else {
properties = p_dictionary;
} }
if (p_dictionary.has("height")) { if (properties.has("radius")) {
gltf_shape->set_height(p_dictionary["height"]); gltf_shape->set_radius(properties["radius"]);
} }
if (p_dictionary.has("size")) { if (properties.has("height")) {
const Array &arr = p_dictionary["size"]; gltf_shape->set_height(properties["height"]);
}
if (properties.has("size")) {
const Array &arr = properties["size"];
if (arr.size() == 3) { if (arr.size() == 3) {
gltf_shape->set_size(Vector3(arr[0], arr[1], arr[2])); gltf_shape->set_size(Vector3(arr[0], arr[1], arr[2]));
} else { } else {
ERR_PRINT("GLTFPhysicsShape: Error parsing the size, it must have exactly 3 numbers."); ERR_PRINT("GLTFPhysicsShape: Error parsing the size, it must have exactly 3 numbers.");
} }
} }
if (p_dictionary.has("isTrigger")) { if (properties.has("isTrigger")) {
gltf_shape->set_is_trigger(p_dictionary["isTrigger"]); gltf_shape->set_is_trigger(properties["isTrigger"]);
} }
if (p_dictionary.has("mesh")) { if (properties.has("mesh")) {
gltf_shape->set_mesh_index(p_dictionary["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."); ERR_PRINT("Error parsing GLTFPhysicsShape: The mesh-based shape type '" + shape_type + "' does not have a valid mesh index.");
} }
return gltf_shape; return gltf_shape;
} }
Dictionary GLTFPhysicsShape::to_dictionary() const { Dictionary GLTFPhysicsShape::to_dictionary() const {
Dictionary d; Dictionary gltf_shape;
d["type"] = shape_type; gltf_shape["type"] = shape_type;
Dictionary sub;
if (shape_type == "box") { if (shape_type == "box") {
Array size_array; Array size_array;
size_array.resize(3); size_array.resize(3);
size_array[0] = size.x; size_array[0] = size.x;
size_array[1] = size.y; size_array[1] = size.y;
size_array[2] = size.z; size_array[2] = size.z;
d["size"] = size_array; sub["size"] = size_array;
} else if (shape_type == "capsule") { } else if (shape_type == "capsule") {
d["radius"] = get_radius(); sub["radius"] = get_radius();
d["height"] = get_height(); sub["height"] = get_height();
} else if (shape_type == "cylinder") { } else if (shape_type == "cylinder") {
d["radius"] = get_radius(); sub["radius"] = get_radius();
d["height"] = get_height(); sub["height"] = get_height();
} else if (shape_type == "sphere") { } else if (shape_type == "sphere") {
d["radius"] = get_radius(); sub["radius"] = get_radius();
} else if (shape_type == "trimesh" || shape_type == "hull") { } else if (shape_type == "trimesh" || shape_type == "convex") {
d["mesh"] = get_mesh_index(); sub["mesh"] = get_mesh_index();
} }
if (is_trigger) { gltf_shape[shape_type] = sub;
d["isTrigger"] = is_trigger; return gltf_shape;
}
return d;
} }

View file

@ -37,8 +37,9 @@
class ImporterMesh; class ImporterMesh;
// GLTFPhysicsShape is an intermediary between OMI_collider and Godot's collision shape nodes. // GLTFPhysicsShape is an intermediary between Godot's collision shape nodes
// https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_collider // and the OMI_physics_shape extension.
// https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_shape
class GLTFPhysicsShape : public Resource { class GLTFPhysicsShape : public Resource {
GDCLASS(GLTFPhysicsShape, Resource) GDCLASS(GLTFPhysicsShape, Resource)