From 571e4189fd1c9110850d49b0056fe2d4ae820af5 Mon Sep 17 00:00:00 2001 From: Aaron Franke Date: Tue, 20 Dec 2022 18:39:30 -0600 Subject: [PATCH] [3.x] Implement physics support in the GLTF module --- modules/gltf/config.py | 2 + modules/gltf/doc_classes/GLTFCollider.xml | 53 +++ modules/gltf/doc_classes/GLTFNode.xml | 13 + modules/gltf/doc_classes/GLTFPhysicsBody.xml | 42 +++ modules/gltf/doc_classes/GLTFSpecGloss.xml | 6 + modules/gltf/doc_classes/GLTFState.xml | 12 + modules/gltf/extensions/SCsub | 1 + .../gltf/extensions/physics/gltf_collider.cpp | 304 ++++++++++++++++++ .../gltf/extensions/physics/gltf_collider.h | 88 +++++ .../gltf_document_extension_physics.cpp | 274 ++++++++++++++++ .../physics/gltf_document_extension_physics.h | 53 +++ .../extensions/physics/gltf_physics_body.cpp | 196 +++++++++++ .../extensions/physics/gltf_physics_body.h | 71 ++++ modules/gltf/register_types.cpp | 10 + 14 files changed, 1125 insertions(+) create mode 100644 modules/gltf/doc_classes/GLTFCollider.xml create mode 100644 modules/gltf/doc_classes/GLTFPhysicsBody.xml create mode 100644 modules/gltf/extensions/physics/gltf_collider.cpp create mode 100644 modules/gltf/extensions/physics/gltf_collider.h create mode 100644 modules/gltf/extensions/physics/gltf_document_extension_physics.cpp create mode 100644 modules/gltf/extensions/physics/gltf_document_extension_physics.h create mode 100644 modules/gltf/extensions/physics/gltf_physics_body.cpp create mode 100644 modules/gltf/extensions/physics/gltf_physics_body.h diff --git a/modules/gltf/config.py b/modules/gltf/config.py index c98e12be4be..1386bee1bfd 100644 --- a/modules/gltf/config.py +++ b/modules/gltf/config.py @@ -13,11 +13,13 @@ def get_doc_classes(): "GLTFAnimation", "GLTFBufferView", "GLTFCamera", + "GLTFCollider", "GLTFDocument", "GLTFDocumentExtension", "GLTFLight", "GLTFMesh", "GLTFNode", + "GLTFPhysicsBody", "GLTFSkeleton", "GLTFSkin", "GLTFSpecGloss", diff --git a/modules/gltf/doc_classes/GLTFCollider.xml b/modules/gltf/doc_classes/GLTFCollider.xml new file mode 100644 index 00000000000..7cc47aeebd7 --- /dev/null +++ b/modules/gltf/doc_classes/GLTFCollider.xml @@ -0,0 +1,53 @@ + + + + Represents a GLTF collider. + + + Represents a collider 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. + + + https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_collider + + + + + + Serializes this GLTFCollider instance into a [Dictionary]. + + + + + + + Converts this GLTFCollider instance into a Godot [CollisionShape] node. + + + + + + The [ArrayMesh] resource of the collider. This is only used when the collider type is "hull" (convex hull) or "trimesh" (concave trimesh). + + + The height of the collider, in meters. This is only used when the collider type is "capsule" or "cylinder". This value should not be negative, and for "capsule" it should be at least twice the radius. + + + If [code]true[/code], indicates that this collider is a trigger. For Godot, this means that the collider should be a child of an Area3D node. + This is the only variable not used in the [method to_node] method, it's intended to be used alongside when deciding where to add the generated node as a child. + + + The index of the collider's mesh in the GLTF file. This is only used when the collider type is "hull" (convex hull) or "trimesh" (concave trimesh). + + + The radius of the collider, in meters. This is only used when the collider type is "capsule", "cylinder", or "sphere". This value should not be negative. + + + The type of shape this collider represents. Valid values are "box", "capsule", "cylinder", "sphere", "hull", and "trimesh". + + + The size of the collider, in meters. This is only used when the collider type is "box", and it represents the "diameter" of the box. This value should not be negative. + + + + + diff --git a/modules/gltf/doc_classes/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml index 80bfba47aa7..c23b90038e6 100644 --- a/modules/gltf/doc_classes/GLTFNode.xml +++ b/modules/gltf/doc_classes/GLTFNode.xml @@ -31,30 +31,43 @@ + If this GLTF node is a camera, the index of the [GLTFCamera] in the [GLTFState] that describes the camera's properties. If -1, this node is not a camera. + The indices of the children nodes in the [GLTFState]. If this GLTF node has no children, this will be an empty array. + How deep into the node hierarchy this node is. A root node will have a height of 0, its children will have a height of 1, and so on. If -1, the height has not been calculated. + This property is unused and does nothing. + If this GLTF node is a light, the index of the [GLTFLight] in the [GLTFState] that describes the light's properties. If -1, this node is not a light. + If this GLTF node is a mesh, the index of the [GLTFMesh] in the [GLTFState] that describes the mesh's properties. If -1, this node is not a mesh. + The index of the parent node in the [GLTFState]. If -1, this node is a root node. + The rotation of the GLTF node relative to its parent. + The scale of the GLTF node relative to its parent. + If this GLTF node has a skeleton, the index of the [GLTFSkeleton] in the [GLTFState] that describes the skeleton's properties. If -1, this node does not have a skeleton. + If this GLTF node has a skin, the index of the [GLTFSkin] in the [GLTFState] that describes the skin's properties. If -1, this node does not have a skin. + The position of the GLTF node relative to its parent. + The transform of the GLTF node relative to its parent. This property is usually unused since the position, rotation, and scale properties are preferred. diff --git a/modules/gltf/doc_classes/GLTFPhysicsBody.xml b/modules/gltf/doc_classes/GLTFPhysicsBody.xml new file mode 100644 index 00000000000..1f4bbe375b2 --- /dev/null +++ b/modules/gltf/doc_classes/GLTFPhysicsBody.xml @@ -0,0 +1,42 @@ + + + + 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. + + + https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_body + + + + + + Serializes this GLTFPhysicsBody instance into a [Dictionary]. + + + + + + Converts this GLTFPhysicsBody instance into a Godot [CollisionObject] node. + + + + + + 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. Valid values are "static", "kinematic", "character", "rigid", "vehicle", and "trigger". + + + The linear velocity of the physics body, in meters per second. This is only used when the body type is "rigid" or "vehicle". + + + The mass of the physics body, in kilograms. This is only used when the body type is "rigid" or "vehicle". + + + + + diff --git a/modules/gltf/doc_classes/GLTFSpecGloss.xml b/modules/gltf/doc_classes/GLTFSpecGloss.xml index e09c15a0667..3ce5a3aca59 100644 --- a/modules/gltf/doc_classes/GLTFSpecGloss.xml +++ b/modules/gltf/doc_classes/GLTFSpecGloss.xml @@ -6,19 +6,25 @@ [b]Note:[/b] This class is only compiled in editor builds. Run-time glTF loading and saving is [i]not[/i] available in exported projects. References to [GLTFSpecGloss] within a script will cause an error in an exported project. + https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness + The reflected diffuse factor of the material. + The diffuse texture. + The glossiness or smoothness of the material. + The specular-glossiness texture. + The specular RGB color of the material. The alpha channel is unused. diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml index 74f005e3023..d55b122c1c7 100644 --- a/modules/gltf/doc_classes/GLTFState.xml +++ b/modules/gltf/doc_classes/GLTFState.xml @@ -1,6 +1,7 @@ + Represents all data of a GLTF file. [b]Note:[/b] This class is only compiled in editor builds. Run-time glTF loading and saving is [i]not[/i] available in exported projects. References to [GLTFState] within a script will cause an error in an exported project. @@ -44,6 +45,7 @@ + Returns an array of all [GLTFAnimation]s in the GLTF file. When importing, these will be generated as animations in an [AnimationPlayer] node. When exporting, these will be generated from Godot [AnimationPlayer] nodes. @@ -54,6 +56,7 @@ + Returns an array of all [GLTFCamera]s in the GLTF file. These are the cameras that the [member GLTFNode.camera] index refers to. @@ -64,6 +67,7 @@ + Returns an array of all [GLTFLight]s in the GLTF file. These are the lights that the [member GLTFNode.light] index refers to. @@ -74,11 +78,13 @@ + Returns an array of all [GLTFMesh]es in the GLTF file. These are the meshes that the [member GLTFNode.mesh] index refers to. + Returns an array of all [GLTFNode]s in the GLTF file. These are the nodes that [member GLTFNode.children] and [member root_nodes] refer to. This includes nodes that may not be generated in the Godot scene, or nodes that may generate multiple Godot scene nodes. @@ -95,11 +101,13 @@ + Returns an array of all [GLTFSkeleton]s in the GLTF file. These are the skeletons that the [member GLTFNode.skeleton] index refers to. + Returns an array of all [GLTFSkin]s in the GLTF file. These are the skins that the [member GLTFNode.skin] index refers to. @@ -116,11 +124,13 @@ + Returns an array of unique animation names. This is only used during the import process. + Returns an array of unique node names. This is used in both the import process and export process. @@ -244,8 +254,10 @@ + The root nodes of the GLTF file. Typically, a GLTF file will only have one scene, and therefore one root node. However, a GLTF file may have multiple scenes and therefore multiple root nodes, which will be generated as siblings of each other and as children of the root node of the generated Godot scene. + The name of the scene. When importing, if not specified, this will be the file name. When exporting, if specified, the scene name will be saved to the GLTF file. diff --git a/modules/gltf/extensions/SCsub b/modules/gltf/extensions/SCsub index ad214bb79c8..105a1736ded 100644 --- a/modules/gltf/extensions/SCsub +++ b/modules/gltf/extensions/SCsub @@ -7,3 +7,4 @@ env_gltf = env_modules.Clone() # Godot source files env_gltf.add_source_files(env.modules_sources, "*.cpp") +env_gltf.add_source_files(env.modules_sources, "physics/*.cpp") diff --git a/modules/gltf/extensions/physics/gltf_collider.cpp b/modules/gltf/extensions/physics/gltf_collider.cpp new file mode 100644 index 00000000000..fab20aeb435 --- /dev/null +++ b/modules/gltf/extensions/physics/gltf_collider.cpp @@ -0,0 +1,304 @@ +/**************************************************************************/ +/* gltf_collider.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "gltf_collider.h" + +#include "../../gltf_state.h" +#include "core/math/convex_hull.h" +#include "scene/3d/area.h" +#include "scene/resources/box_shape.h" +#include "scene/resources/capsule_shape.h" +#include "scene/resources/concave_polygon_shape.h" +#include "scene/resources/convex_polygon_shape.h" +#include "scene/resources/cylinder_shape.h" +#include "scene/resources/sphere_shape.h" + +void GLTFCollider::_bind_methods() { + ClassDB::bind_method(D_METHOD("to_node", "cache_shapes"), &GLTFCollider::to_node, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("to_dictionary"), &GLTFCollider::to_dictionary); + + ClassDB::bind_method(D_METHOD("get_shape_type"), &GLTFCollider::get_shape_type); + ClassDB::bind_method(D_METHOD("set_shape_type", "shape_type"), &GLTFCollider::set_shape_type); + ClassDB::bind_method(D_METHOD("get_size"), &GLTFCollider::get_size); + ClassDB::bind_method(D_METHOD("set_size", "size"), &GLTFCollider::set_size); + ClassDB::bind_method(D_METHOD("get_radius"), &GLTFCollider::get_radius); + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &GLTFCollider::set_radius); + ClassDB::bind_method(D_METHOD("get_height"), &GLTFCollider::get_height); + ClassDB::bind_method(D_METHOD("set_height", "height"), &GLTFCollider::set_height); + ClassDB::bind_method(D_METHOD("get_is_trigger"), &GLTFCollider::get_is_trigger); + ClassDB::bind_method(D_METHOD("set_is_trigger", "is_trigger"), &GLTFCollider::set_is_trigger); + ClassDB::bind_method(D_METHOD("get_mesh_index"), &GLTFCollider::get_mesh_index); + ClassDB::bind_method(D_METHOD("set_mesh_index", "mesh_index"), &GLTFCollider::set_mesh_index); + ClassDB::bind_method(D_METHOD("get_array_mesh"), &GLTFCollider::get_array_mesh); + ClassDB::bind_method(D_METHOD("set_array_mesh", "array_mesh"), &GLTFCollider::set_array_mesh); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "shape_type"), "set_shape_type", "get_shape_type"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "height"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_trigger"), "set_is_trigger", "get_is_trigger"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "mesh_index"), "set_mesh_index", "get_mesh_index"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "array_mesh", PROPERTY_HINT_RESOURCE_TYPE, "ArrayMesh"), "set_array_mesh", "get_array_mesh"); +} + +String GLTFCollider::get_shape_type() const { + return shape_type; +} + +void GLTFCollider::set_shape_type(String p_shape_type) { + shape_type = p_shape_type; +} + +Vector3 GLTFCollider::get_size() const { + return size; +} + +void GLTFCollider::set_size(Vector3 p_size) { + size = p_size; +} + +real_t GLTFCollider::get_radius() const { + return radius; +} + +void GLTFCollider::set_radius(real_t p_radius) { + radius = p_radius; +} + +real_t GLTFCollider::get_height() const { + return height; +} + +void GLTFCollider::set_height(real_t p_height) { + height = p_height; +} + +bool GLTFCollider::get_is_trigger() const { + return is_trigger; +} + +void GLTFCollider::set_is_trigger(bool p_is_trigger) { + is_trigger = p_is_trigger; +} + +GLTFMeshIndex GLTFCollider::get_mesh_index() const { + return mesh_index; +} + +void GLTFCollider::set_mesh_index(GLTFMeshIndex p_mesh_index) { + mesh_index = p_mesh_index; +} + +Ref GLTFCollider::get_array_mesh() const { + return array_mesh; +} + +void GLTFCollider::set_array_mesh(Ref p_array_mesh) { + array_mesh = p_array_mesh; +} + +Ref GLTFCollider::from_node(const CollisionShape *p_collider_node) { + Ref collider; + collider.instance(); + ERR_FAIL_NULL_V_MSG(p_collider_node, collider, "Tried to create a GLTFCollider from a CollisionShape node, but the given node was null."); + Node *parent = p_collider_node->get_parent(); + if (cast_to(parent)) { + collider->set_is_trigger(true); + } + // All the code for working with the shape is below this comment. + Ref shape = p_collider_node->get_shape(); + ERR_FAIL_COND_V_MSG(shape.is_null(), collider, "Tried to create a GLTFCollider from a CollisionShape node, but the given node had a null shape."); + collider->_shape_cache = shape; + if (cast_to(shape.ptr())) { + collider->shape_type = "box"; + Ref box = shape; + collider->set_size(box->get_extents() * 2.0f); + } else if (cast_to(shape.ptr())) { + collider->shape_type = "capsule"; + Ref capsule = shape; + collider->set_radius(capsule->get_radius()); + collider->set_height(capsule->get_height()); + } else if (cast_to(shape.ptr())) { + collider->shape_type = "cylinder"; + Ref cylinder = shape; + collider->set_radius(cylinder->get_radius()); + collider->set_height(cylinder->get_height()); + } else if (cast_to(shape.ptr())) { + collider->shape_type = "sphere"; + Ref sphere = shape; + collider->set_radius(sphere->get_radius()); + } else if (cast_to(shape.ptr())) { + collider->shape_type = "hull"; + Ref convex = shape; + PoolVector hull_points = convex->get_points(); + ERR_FAIL_COND_V_MSG(hull_points.size() < 3, collider, "GLTFCollider: 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."); + if (hull_points.size() > 255) { + WARN_PRINT("GLTFCollider: Convex hull has more points (" + itos(hull_points.size()) + ") than the recommended maximum of 255. This may not load correctly in other engines."); + } + // Convert the convex hull points into an array of faces. + Geometry::MeshData md; + Error err = ConvexHullComputer::convex_hull(hull_points, md); + ERR_FAIL_COND_V_MSG(err != OK, collider, "GLTFCollider: Failed to compute convex hull."); + Vector face_vertices; + for (uint32_t i = 0; i < (uint32_t)md.faces.size(); i++) { + uint32_t index_count = md.faces[i].indices.size(); + for (uint32_t j = 1; j < index_count - 1; j++) { + face_vertices.push_back(hull_points[md.faces[i].indices[0]]); + face_vertices.push_back(hull_points[md.faces[i].indices[j]]); + face_vertices.push_back(hull_points[md.faces[i].indices[j + 1]]); + } + } + // Create an ArrayMesh from the faces. + Ref array_mesh; + array_mesh.instance(); + Array surface_array; + surface_array.resize(Mesh::ArrayType::ARRAY_MAX); + surface_array[Mesh::ArrayType::ARRAY_VERTEX] = face_vertices; + array_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, surface_array); + collider->set_array_mesh(array_mesh); + } else if (cast_to(shape.ptr())) { + collider->shape_type = "trimesh"; + Ref concave = shape; + Ref array_mesh; + array_mesh.instance(); + Array surface_array; + surface_array.resize(Mesh::ArrayType::ARRAY_MAX); + surface_array[Mesh::ArrayType::ARRAY_VERTEX] = concave->get_faces(); + array_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, surface_array); + collider->set_array_mesh(array_mesh); + } else { + ERR_PRINT("Tried to create a GLTFCollider from a CollisionShape node, but the given node's shape '" + String(Variant(shape)) + + "' had an unsupported shape type. Only BoxShape, CapsuleShape, CylinderShape, SphereShape, ConcavePolygonShape, and ConvexPolygonShape are supported."); + } + return collider; +} + +CollisionShape *GLTFCollider::to_node(bool p_cache_shapes) { + CollisionShape *collider = memnew(CollisionShape); + if (!p_cache_shapes || _shape_cache == nullptr) { + if (shape_type == "box") { + Ref box; + box.instance(); + box->set_extents(size * 0.5f); + _shape_cache = box; + } else if (shape_type == "capsule") { + Ref capsule; + capsule.instance(); + capsule->set_radius(radius); + capsule->set_height(height); + _shape_cache = capsule; + } else if (shape_type == "cylinder") { + Ref cylinder; + cylinder.instance(); + cylinder->set_radius(radius); + cylinder->set_height(height); + _shape_cache = cylinder; + } else if (shape_type == "sphere") { + Ref sphere; + sphere.instance(); + sphere->set_radius(radius); + _shape_cache = sphere; + } else if (shape_type == "hull") { + ERR_FAIL_COND_V_MSG(array_mesh.is_null(), collider, "GLTFCollider: Error converting convex hull collider to a node: The mesh resource is null."); + Ref convex = array_mesh->create_convex_shape(); + _shape_cache = convex; + } else if (shape_type == "trimesh") { + ERR_FAIL_COND_V_MSG(array_mesh.is_null(), collider, "GLTFCollider: Error converting concave mesh collider to a node: The mesh resource is null."); + Ref concave = array_mesh->create_trimesh_shape(); + _shape_cache = concave; + } else { + ERR_PRINT("GLTFCollider: Error converting to a node: Shape type '" + shape_type + "' is unknown."); + } + } + collider->set_shape(_shape_cache); + return collider; +} + +Ref GLTFCollider::from_dictionary(const Dictionary p_dictionary) { + ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref(), "Failed to parse GLTF collider, missing required field 'type'."); + Ref collider; + collider.instance(); + const String &shape_type = p_dictionary["type"]; + collider->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("Error parsing GLTF collider: Shape type '" + shape_type + "' is unknown. Only box, capsule, cylinder, sphere, hull, and trimesh are supported."); + } + if (p_dictionary.has("radius")) { + collider->set_radius(p_dictionary["radius"]); + } + if (p_dictionary.has("height")) { + collider->set_height(p_dictionary["height"]); + } + if (p_dictionary.has("size")) { + const Array &arr = p_dictionary["size"]; + if (arr.size() == 3) { + collider->set_size(Vector3(arr[0], arr[1], arr[2])); + } else { + ERR_PRINT("Error parsing GLTF collider: The size must have exactly 3 numbers."); + } + } + if (p_dictionary.has("isTrigger")) { + collider->set_is_trigger(p_dictionary["isTrigger"]); + } + if (p_dictionary.has("mesh")) { + collider->set_mesh_index(p_dictionary["mesh"]); + } + if (unlikely(collider->get_mesh_index() < 0 && (shape_type == "hull" || shape_type == "trimesh"))) { + ERR_PRINT("Error parsing GLTF collider: The mesh-based shape type '" + shape_type + "' does not have a valid mesh index."); + } + return collider; +} + +Dictionary GLTFCollider::to_dictionary() const { + Dictionary d; + d["type"] = shape_type; + 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; + } else if (shape_type == "capsule") { + d["radius"] = get_radius(); + d["height"] = get_height(); + } else if (shape_type == "cylinder") { + d["radius"] = get_radius(); + d["height"] = get_height(); + } else if (shape_type == "sphere") { + d["radius"] = get_radius(); + } else if (shape_type == "trimesh" || shape_type == "hull") { + d["mesh"] = get_mesh_index(); + } + if (is_trigger) { + d["isTrigger"] = is_trigger; + } + return d; +} diff --git a/modules/gltf/extensions/physics/gltf_collider.h b/modules/gltf/extensions/physics/gltf_collider.h new file mode 100644 index 00000000000..13086946821 --- /dev/null +++ b/modules/gltf/extensions/physics/gltf_collider.h @@ -0,0 +1,88 @@ +/**************************************************************************/ +/* gltf_collider.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef GLTF_COLLIDER_H +#define GLTF_COLLIDER_H + +#include "../../gltf_defines.h" +#include "scene/3d/collision_shape.h" + +class GLTFState; + +// GLTFCollider 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 + +class GLTFCollider : public Resource { + GDCLASS(GLTFCollider, Resource) + +protected: + static void _bind_methods(); + +private: + String shape_type; + Vector3 size = Vector3(1.0, 1.0, 1.0); + real_t radius = 0.5; + real_t height = 2.0; + bool is_trigger = false; + GLTFMeshIndex mesh_index = -1; + Ref array_mesh = nullptr; + // Internal only, for caching Godot shape resources. Used in `to_node`. + Ref _shape_cache = nullptr; + +public: + String get_shape_type() const; + void set_shape_type(String p_shape_type); + + Vector3 get_size() const; + void set_size(Vector3 p_size); + + real_t get_radius() const; + void set_radius(real_t p_radius); + + real_t get_height() const; + void set_height(real_t p_height); + + bool get_is_trigger() const; + void set_is_trigger(bool p_is_trigger); + + GLTFMeshIndex get_mesh_index() const; + void set_mesh_index(GLTFMeshIndex p_mesh_index); + + Ref get_array_mesh() const; + void set_array_mesh(Ref p_array_mesh); + + static Ref from_node(const CollisionShape *p_collider_node); + CollisionShape *to_node(bool p_cache_shapes = false); + + static Ref from_dictionary(const Dictionary p_dictionary); + Dictionary to_dictionary() const; +}; + +#endif // GLTF_COLLIDER_H diff --git a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp new file mode 100644 index 00000000000..94d2fef7319 --- /dev/null +++ b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp @@ -0,0 +1,274 @@ +/**************************************************************************/ +/* gltf_document_extension_physics.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "gltf_document_extension_physics.h" + +#include "scene/3d/area.h" + +// Import process. +Error GLTFDocumentExtensionPhysics::import_preflight(Ref p_state, Vector p_extensions) { + if (p_extensions.find("OMI_collider") < 0 && p_extensions.find("OMI_physics_body") < 0) { + 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")) { + Dictionary omi_collider_ext = state_extensions["OMI_collider"]; + if (omi_collider_ext.has("colliders")) { + Array state_collider_dicts = omi_collider_ext["colliders"]; + if (state_collider_dicts.size() > 0) { + Array state_colliders; + for (int i = 0; i < state_collider_dicts.size(); i++) { + state_colliders.push_back(GLTFCollider::from_dictionary(state_collider_dicts[i])); + } + p_state->set_additional_data("GLTFColliders", state_colliders); + } + } + } + } + return OK; +} + +Vector GLTFDocumentExtensionPhysics::get_supported_extensions() { + Vector ret; + ret.push_back("OMI_collider"); + ret.push_back("OMI_physics_body"); + return ret; +} + +Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref p_state, Ref p_gltf_node, Dictionary &p_extensions) { + 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("GLTFColliders"); + 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("GLTFCollider", state_colliders[node_collider_index]); + } else { + p_gltf_node->set_additional_data("GLTFCollider", GLTFCollider::from_dictionary(p_extensions["OMI_collider"])); + } + } + if (p_extensions.has("OMI_physics_body")) { + p_gltf_node->set_additional_data("GLTFPhysicsBody", GLTFPhysicsBody::from_dictionary(p_extensions["OMI_physics_body"])); + } + 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. + } + Ref array_mesh = p_collider->get_array_mesh(); + if (array_mesh.is_valid()) { + return; // The mesh resource is already set up. + } + Array 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_COND(gltf_mesh.is_null()); + array_mesh = gltf_mesh->get_mesh(); + ERR_FAIL_COND(array_mesh.is_null()); + p_collider->set_array_mesh(array_mesh); +} + +CollisionObject *_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(); + // 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 + // this particular node is not using it. Another possibility is that the + // physics body information is set up on the same GLTF node, not a parent. + CollisionObject *body; + if (p_physics_body.is_valid()) { + // This code is run when the physics body is on the same GLTF node. + body = p_physics_body->to_node(); + if (is_trigger != (p_physics_body->get_body_type() == "trigger")) { + // Edge case: If the body's trigger and the collider's trigger + // are in disagreement, we need to create another new body. + CollisionObject *child = _generate_collision_with_body(p_state, p_gltf_node, p_collider, nullptr); + child->set_name(p_gltf_node->get_name() + (is_trigger ? String("Trigger") : String("Solid"))); + body->add_child(child); + return body; + } + } else if (is_trigger) { + body = memnew(Area); + } else { + body = memnew(StaticBody); + } + CollisionShape *shape = p_collider->to_node(); + shape->set_name(p_gltf_node->get_name() + "Shape"); + body->add_child(shape); + return body; +} + +Spatial *GLTFDocumentExtensionPhysics::generate_scene_node(Ref p_state, Ref p_gltf_node, Node *p_scene_parent) { + Ref physics_body = p_gltf_node->get_additional_data("GLTFPhysicsBody"); + Ref collider = p_gltf_node->get_additional_data("GLTFCollider"); + 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); + } + } + 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; +} + +// Export process. +bool _are_all_faces_equal(const PoolVector &p_a, const PoolVector &p_b) { + if (p_a.size() != p_b.size()) { + return false; + } + for (int i = 0; i < p_a.size(); i++) { + Face3 a_face = p_a[i]; + Face3 b_face = p_b[i]; + const Vector3 *a_vertices = a_face.vertex; + const Vector3 *b_vertices = b_face.vertex; + for (int j = 0; j < 3; j++) { + if (!a_vertices[j].is_equal_approx(b_vertices[j])) { + return false; + } + } + } + return true; +} + +GLTFMeshIndex _get_or_insert_mesh_in_state(Ref p_state, Ref p_mesh) { + ERR_FAIL_COND_V(p_mesh.is_null(), -1); + Array state_meshes = p_state->get_meshes(); + PoolVector mesh_faces = p_mesh->get_faces(); + // De-duplication: If the state already has the mesh we need, use that one. + for (GLTFMeshIndex i = 0; i < state_meshes.size(); i++) { + Ref state_gltf_mesh = state_meshes[i]; + ERR_CONTINUE(state_gltf_mesh.is_null()); + Ref state_array_mesh = state_gltf_mesh->get_mesh(); + ERR_CONTINUE(state_array_mesh.is_null()); + if (state_array_mesh == p_mesh) { + return i; + } + if (_are_all_faces_equal(state_array_mesh->get_faces(), mesh_faces)) { + return i; + } + } + // After the loop, we have checked that the mesh is not equal to any of the + // meshes in the state. So we insert a new mesh into the state mesh array. + Ref gltf_mesh; + gltf_mesh.instance(); + gltf_mesh->set_mesh(p_mesh); + GLTFMeshIndex mesh_index = state_meshes.size(); + state_meshes.push_back(gltf_mesh); + p_state->set_meshes(state_meshes); + return mesh_index; +} + +void GLTFDocumentExtensionPhysics::convert_scene_node(Ref p_state, Ref p_gltf_node, Node *p_scene_node) { + if (cast_to(p_scene_node)) { + CollisionShape *shape = Object::cast_to(p_scene_node); + Ref collider = GLTFCollider::from_node(shape); + { + Ref array_mesh = collider->get_array_mesh(); + if (array_mesh.is_valid()) { + collider->set_mesh_index(_get_or_insert_mesh_in_state(p_state, array_mesh)); + } + } + p_gltf_node->set_additional_data("GLTFCollider", collider); + } else if (cast_to(p_scene_node)) { + CollisionObject *body = Object::cast_to(p_scene_node); + p_gltf_node->set_additional_data("GLTFPhysicsBody", GLTFPhysicsBody::from_node(body)); + } +} + +Array _get_or_create_state_colliders_in_state(Ref p_state) { + Dictionary state_json = p_state->get_json(); + Dictionary state_extensions; + if (state_json.has("extensions")) { + state_extensions = state_json["extensions"]; + } else { + state_json["extensions"] = state_extensions; + } + Dictionary omi_collider_ext; + if (state_extensions.has("OMI_collider")) { + omi_collider_ext = state_extensions["OMI_collider"]; + } else { + state_extensions["OMI_collider"] = omi_collider_ext; + p_state->add_used_extension("OMI_collider"); + } + Array state_colliders; + if (omi_collider_ext.has("colliders")) { + state_colliders = omi_collider_ext["colliders"]; + } else { + omi_collider_ext["colliders"] = state_colliders; + } + return state_colliders; +} + +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"]; + Ref physics_body = p_gltf_node->get_additional_data("GLTFPhysicsBody"); + if (physics_body.is_valid()) { + node_extensions["OMI_physics_body"] = physics_body->to_dictionary(); + p_state->add_used_extension("OMI_physics_body"); + } + Ref collider = p_gltf_node->get_additional_data("GLTFCollider"); + 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; + } + return OK; +} diff --git a/modules/gltf/extensions/physics/gltf_document_extension_physics.h b/modules/gltf/extensions/physics/gltf_document_extension_physics.h new file mode 100644 index 00000000000..9a4f50fe3aa --- /dev/null +++ b/modules/gltf/extensions/physics/gltf_document_extension_physics.h @@ -0,0 +1,53 @@ +/**************************************************************************/ +/* gltf_document_extension_physics.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef GLTF_DOCUMENT_EXTENSION_PHYSICS_H +#define GLTF_DOCUMENT_EXTENSION_PHYSICS_H + +#include "../gltf_document_extension.h" + +#include "gltf_collider.h" +#include "gltf_physics_body.h" + +class GLTFDocumentExtensionPhysics : public GLTFDocumentExtension { + GDCLASS(GLTFDocumentExtensionPhysics, GLTFDocumentExtension); + +public: + // Import process. + Error import_preflight(Ref p_state, Vector p_extensions); + Vector get_supported_extensions(); + Error parse_node_extensions(Ref p_state, Ref p_gltf_node, Dictionary &p_extensions); + Spatial *generate_scene_node(Ref p_state, Ref p_gltf_node, Node *p_scene_parent); + // Export process. + void convert_scene_node(Ref p_state, Ref p_gltf_node, Node *p_scene_node); + Error export_node(Ref p_state, Ref p_gltf_node, Dictionary &r_node_json, Node *p_scene_node); +}; + +#endif // GLTF_DOCUMENT_EXTENSION_PHYSICS_H diff --git a/modules/gltf/extensions/physics/gltf_physics_body.cpp b/modules/gltf/extensions/physics/gltf_physics_body.cpp new file mode 100644 index 00000000000..92e8b9cec75 --- /dev/null +++ b/modules/gltf/extensions/physics/gltf_physics_body.cpp @@ -0,0 +1,196 @@ +/**************************************************************************/ +/* gltf_physics_body.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "gltf_physics_body.h" + +#include "scene/3d/area.h" +#include "scene/3d/vehicle_body.h" + +void GLTFPhysicsBody::_bind_methods() { + ClassDB::bind_method(D_METHOD("to_node"), &GLTFPhysicsBody::to_node); + ClassDB::bind_method(D_METHOD("to_dictionary"), &GLTFPhysicsBody::to_dictionary); + + ClassDB::bind_method(D_METHOD("get_body_type"), &GLTFPhysicsBody::get_body_type); + ClassDB::bind_method(D_METHOD("set_body_type", "body_type"), &GLTFPhysicsBody::set_body_type); + ClassDB::bind_method(D_METHOD("get_mass"), &GLTFPhysicsBody::get_mass); + ClassDB::bind_method(D_METHOD("set_mass", "mass"), &GLTFPhysicsBody::set_mass); + ClassDB::bind_method(D_METHOD("get_linear_velocity"), &GLTFPhysicsBody::get_linear_velocity); + ClassDB::bind_method(D_METHOD("set_linear_velocity", "linear_velocity"), &GLTFPhysicsBody::set_linear_velocity); + ClassDB::bind_method(D_METHOD("get_angular_velocity"), &GLTFPhysicsBody::get_angular_velocity); + ClassDB::bind_method(D_METHOD("set_angular_velocity", "angular_velocity"), &GLTFPhysicsBody::set_angular_velocity); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "body_type"), "set_body_type", "get_body_type"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "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"); +} + +String GLTFPhysicsBody::get_body_type() const { + return body_type; +} + +void GLTFPhysicsBody::set_body_type(String p_body_type) { + body_type = p_body_type; +} + +real_t GLTFPhysicsBody::get_mass() const { + return mass; +} + +void GLTFPhysicsBody::set_mass(real_t p_mass) { + mass = p_mass; +} + +Vector3 GLTFPhysicsBody::get_linear_velocity() const { + return linear_velocity; +} + +void GLTFPhysicsBody::set_linear_velocity(Vector3 p_linear_velocity) { + linear_velocity = p_linear_velocity; +} + +Vector3 GLTFPhysicsBody::get_angular_velocity() const { + return angular_velocity; +} + +void GLTFPhysicsBody::set_angular_velocity(Vector3 p_angular_velocity) { + angular_velocity = p_angular_velocity; +} + +Ref GLTFPhysicsBody::from_node(const CollisionObject *p_body_node) { + Ref physics_body; + physics_body.instance(); + ERR_FAIL_COND_V_MSG(!p_body_node, physics_body, "Tried to create a GLTFPhysicsBody from a CollisionObject node, but the given node was null."); + if (cast_to(p_body_node)) { + physics_body->body_type = "kinematic"; + } else if (cast_to(p_body_node)) { + const RigidBody *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(); + if (cast_to(p_body_node)) { + physics_body->body_type = "vehicle"; + } else { + physics_body->body_type = "rigid"; + } + } else if (cast_to(p_body_node)) { + physics_body->body_type = "static"; + } else if (cast_to(p_body_node)) { + physics_body->body_type = "trigger"; + } + return physics_body; +} + +CollisionObject *GLTFPhysicsBody::to_node() const { + if (body_type == "character" || body_type == "kinematic") { + KinematicBody *body = memnew(KinematicBody); + return body; + } + if (body_type == "vehicle") { + VehicleBody *body = memnew(VehicleBody); + body->set_mass(mass); + body->set_linear_velocity(linear_velocity); + body->set_angular_velocity(angular_velocity); + return body; + } + if (body_type == "rigid") { + RigidBody *body = memnew(RigidBody); + body->set_mass(mass); + body->set_linear_velocity(linear_velocity); + body->set_angular_velocity(angular_velocity); + return body; + } + if (body_type == "static") { + StaticBody *body = memnew(StaticBody); + return body; + } + if (body_type == "trigger") { + Area *body = memnew(Area); + return body; + } + ERR_FAIL_V_MSG(nullptr, "Error converting GLTFPhysicsBody to a node: Body type '" + body_type + "' is unknown."); +} + +Ref GLTFPhysicsBody::from_dictionary(const Dictionary p_dictionary) { + Ref physics_body; + physics_body.instance(); + 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"]; + } + if (p_dictionary.has("linearVelocity")) { + const Array &arr = p_dictionary["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 (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 (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."); + } + return physics_body; +} + +Dictionary GLTFPhysicsBody::to_dictionary() const { + Dictionary d; + d["type"] = body_type; + if (mass != 1.0) { + d["mass"] = mass; + } + if (linear_velocity != Vector3()) { + Array velocity_array; + velocity_array.resize(3); + velocity_array[0] = linear_velocity.x; + velocity_array[1] = linear_velocity.y; + velocity_array[2] = linear_velocity.z; + d["linearVelocity"] = velocity_array; + } + if (angular_velocity != Vector3()) { + Array velocity_array; + velocity_array.resize(3); + velocity_array[0] = angular_velocity.x; + velocity_array[1] = angular_velocity.y; + velocity_array[2] = angular_velocity.z; + d["angularVelocity"] = velocity_array; + } + return d; +} diff --git a/modules/gltf/extensions/physics/gltf_physics_body.h b/modules/gltf/extensions/physics/gltf_physics_body.h new file mode 100644 index 00000000000..b496e0307de --- /dev/null +++ b/modules/gltf/extensions/physics/gltf_physics_body.h @@ -0,0 +1,71 @@ +/**************************************************************************/ +/* gltf_physics_body.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef GLTF_PHYSICS_BODY_H +#define GLTF_PHYSICS_BODY_H + +#include "scene/3d/physics_body.h" + +// GLTFPhysicsBody is an intermediary between OMI_physics_body and Godot's physics body nodes. +// https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_body + +class GLTFPhysicsBody : public Resource { + GDCLASS(GLTFPhysicsBody, Resource) + +protected: + static void _bind_methods(); + +private: + String body_type = "static"; + real_t mass = 1.0; + Vector3 linear_velocity = Vector3(); + Vector3 angular_velocity = Vector3(); + +public: + String get_body_type() const; + void set_body_type(String p_body_type); + + real_t get_mass() const; + void set_mass(real_t p_mass); + + Vector3 get_linear_velocity() const; + void set_linear_velocity(Vector3 p_linear_velocity); + + Vector3 get_angular_velocity() const; + void set_angular_velocity(Vector3 p_angular_velocity); + + static Ref from_node(const CollisionObject *p_body_node); + CollisionObject *to_node() const; + + static Ref from_dictionary(const Dictionary p_dictionary); + Dictionary to_dictionary() const; +}; + +#endif // GLTF_PHYSICS_BODY_H diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp index d6be7f5e6ef..69985c317c2 100644 --- a/modules/gltf/register_types.cpp +++ b/modules/gltf/register_types.cpp @@ -34,6 +34,7 @@ #include "extensions/gltf_document_extension.h" #include "extensions/gltf_spec_gloss.h" +#include "extensions/physics/gltf_document_extension_physics.h" #include "gltf_state.h" #ifdef TOOLS_ENABLED @@ -50,6 +51,11 @@ static void _editor_init() { } #endif +#define GLTF_REGISTER_DOCUMENT_EXTENSION(m_doc_ext_class) \ + Ref extension_##m_doc_ext_class; \ + extension_##m_doc_ext_class.instance(); \ + GLTFDocument::register_gltf_document_extension(extension_##m_doc_ext_class); + void register_gltf_types() { #ifdef TOOLS_ENABLED ClassDB::APIType prev_api = ClassDB::get_current_api(); @@ -66,19 +72,23 @@ void register_gltf_types() { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionPhysics); } void unregister_gltf_types() { + GLTFDocument::unregister_all_gltf_document_extensions(); } #endif // _3D_DISABLED