diff --git a/modules/gltf/config.py b/modules/gltf/config.py index 130c06d264c9..36cf053aa177 100644 --- a/modules/gltf/config.py +++ b/modules/gltf/config.py @@ -15,12 +15,14 @@ def get_doc_classes(): "GLTFAnimation", "GLTFBufferView", "GLTFCamera", + "GLTFCollider", "GLTFDocument", "GLTFDocumentExtension", "GLTFDocumentExtensionConvertImporterMesh", "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 000000000000..cd397df3980f --- /dev/null +++ b/modules/gltf/doc_classes/GLTFCollider.xml @@ -0,0 +1,65 @@ + + + + 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 + + + + + + + Creates a new GLTFCollider instance by parsing the given [Dictionary]. + + + + + + + Create a new GLTFCollider instance from the given Godot [CollisionShape3D] node. + + + + + + Serializes this GLTFCollider instance into a [Dictionary]. + + + + + + + Converts this GLTFCollider instance into a Godot [CollisionShape3D] node. + + + + + + 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. + + + The [ImporterMesh] resource of the collider. This is only used when the collider type is "hull" (convex hull) or "trimesh" (concave trimesh). + + + 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/GLTFPhysicsBody.xml b/modules/gltf/doc_classes/GLTFPhysicsBody.xml new file mode 100644 index 000000000000..054bd2fc858b --- /dev/null +++ b/modules/gltf/doc_classes/GLTFPhysicsBody.xml @@ -0,0 +1,58 @@ + + + + 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 + + + + + + + Creates a new GLTFPhysicsBody instance by parsing the given [Dictionary]. + + + + + + + Create a new GLTFPhysicsBody instance from the given Godot [CollisionObject3D] node. + + + + + + Serializes this GLTFPhysicsBody instance into a [Dictionary]. + + + + + + Converts this GLTFPhysicsBody instance into a Godot [CollisionObject3D] 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 principle axes of 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". + This is written to and read from the GLTF file as a 3x3 matrix, but is exposed as a Vector3 since Godot only supports principal axis inertia values. When converted to a Godot [RigidBody3D] node, if this value is zero, then the inertia will be calculated automatically. + + + 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/extensions/SCsub b/modules/gltf/extensions/SCsub index ad214bb79c87..105a1736dedb 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 000000000000..1a42a84874c0 --- /dev/null +++ b/modules/gltf/extensions/physics/gltf_collider.cpp @@ -0,0 +1,308 @@ +/**************************************************************************/ +/* 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_3d.h" +#include "scene/resources/box_shape_3d.h" +#include "scene/resources/capsule_shape_3d.h" +#include "scene/resources/concave_polygon_shape_3d.h" +#include "scene/resources/convex_polygon_shape_3d.h" +#include "scene/resources/cylinder_shape_3d.h" +#include "scene/resources/importer_mesh.h" +#include "scene/resources/sphere_shape_3d.h" + +void GLTFCollider::_bind_methods() { + ClassDB::bind_static_method("GLTFCollider", D_METHOD("from_node", "collider_node"), &GLTFCollider::from_node); + ClassDB::bind_method(D_METHOD("to_node", "cache_shapes"), &GLTFCollider::to_node, DEFVAL(false)); + + ClassDB::bind_static_method("GLTFCollider", D_METHOD("from_dictionary", "dictionary"), &GLTFCollider::from_dictionary); + 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_importer_mesh"), &GLTFCollider::get_importer_mesh); + ClassDB::bind_method(D_METHOD("set_importer_mesh", "importer_mesh"), &GLTFCollider::set_importer_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::FLOAT, "radius"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "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, "importer_mesh", PROPERTY_HINT_RESOURCE_TYPE, "ImporterMesh"), "set_importer_mesh", "get_importer_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_importer_mesh() const { + return importer_mesh; +} + +void GLTFCollider::set_importer_mesh(Ref p_importer_mesh) { + importer_mesh = p_importer_mesh; +} + +Ref GLTFCollider::from_node(const CollisionShape3D *p_collider_node) { + Ref collider; + collider.instantiate(); + ERR_FAIL_NULL_V_MSG(p_collider_node, collider, "Tried to create a GLTFCollider from a CollisionShape3D 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 CollisionShape3D 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_size()); + } 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; + Vector 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. + Geometry3D::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 < 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.append(hull_points[md.faces[i].indices[0]]); + face_vertices.append(hull_points[md.faces[i].indices[j]]); + face_vertices.append(hull_points[md.faces[i].indices[j + 1]]); + } + } + // Create an ImporterMesh from the faces. + Ref importer_mesh; + importer_mesh.instantiate(); + Array surface_array; + surface_array.resize(Mesh::ArrayType::ARRAY_MAX); + surface_array[Mesh::ArrayType::ARRAY_VERTEX] = face_vertices; + importer_mesh->add_surface(Mesh::PRIMITIVE_TRIANGLES, surface_array); + collider->set_importer_mesh(importer_mesh); + } else if (cast_to(shape.ptr())) { + collider->shape_type = "trimesh"; + Ref concave = shape; + Ref importer_mesh; + importer_mesh.instantiate(); + Array surface_array; + surface_array.resize(Mesh::ArrayType::ARRAY_MAX); + surface_array[Mesh::ArrayType::ARRAY_VERTEX] = concave->get_faces(); + importer_mesh->add_surface(Mesh::PRIMITIVE_TRIANGLES, surface_array); + collider->set_importer_mesh(importer_mesh); + } else { + ERR_PRINT("Tried to create a GLTFCollider from a CollisionShape3D node, but the given node's shape '" + String(Variant(shape)) + + "' had an unsupported shape type. Only BoxShape3D, CapsuleShape3D, CylinderShape3D, SphereShape3D, ConcavePolygonShape3D, and ConvexPolygonShape3D are supported."); + } + return collider; +} + +CollisionShape3D *GLTFCollider::to_node(bool p_cache_shapes) { + CollisionShape3D *collider = memnew(CollisionShape3D); + if (!p_cache_shapes || _shape_cache == nullptr) { + if (shape_type == "box") { + Ref box; + box.instantiate(); + box->set_size(size); + _shape_cache = box; + } else if (shape_type == "capsule") { + Ref capsule; + capsule.instantiate(); + capsule->set_radius(radius); + capsule->set_height(height); + _shape_cache = capsule; + } else if (shape_type == "cylinder") { + Ref cylinder; + cylinder.instantiate(); + cylinder->set_radius(radius); + cylinder->set_height(height); + _shape_cache = cylinder; + } else if (shape_type == "sphere") { + Ref sphere; + sphere.instantiate(); + sphere->set_radius(radius); + _shape_cache = sphere; + } else if (shape_type == "hull") { + ERR_FAIL_COND_V_MSG(importer_mesh.is_null(), collider, "GLTFCollider: Error converting convex hull collider to a node: The mesh resource is null."); + Ref convex = importer_mesh->get_mesh()->create_convex_shape(); + _shape_cache = convex; + } else if (shape_type == "trimesh") { + ERR_FAIL_COND_V_MSG(importer_mesh.is_null(), collider, "GLTFCollider: Error converting concave mesh collider to a node: The mesh resource is null."); + Ref concave = importer_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.instantiate(); + 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 000000000000..94c701161814 --- /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_3d.h" + +class ImporterMesh; + +// 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 importer_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_importer_mesh() const; + void set_importer_mesh(Ref p_importer_mesh); + + static Ref from_node(const CollisionShape3D *p_collider_node); + CollisionShape3D *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 000000000000..e36a9fbb950c --- /dev/null +++ b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp @@ -0,0 +1,272 @@ +/**************************************************************************/ +/* 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_3d.h" + +// Import process. +Error GLTFDocumentExtensionPhysics::import_preflight(Ref p_state, Vector p_extensions) { + if (!p_extensions.has("OMI_collider") && !p_extensions.has("OMI_physics_body")) { + 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(StringName("GLTFCollider"), state_colliders[node_collider_index]); + } else { + p_gltf_node->set_additional_data(StringName("GLTFCollider"), GLTFCollider::from_dictionary(p_extensions["OMI_collider"])); + } + } + if (p_extensions.has("OMI_physics_body")) { + p_gltf_node->set_additional_data(StringName("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 importer_mesh = p_collider->get_importer_mesh(); + if (importer_mesh.is_valid()) { + return; // The mesh resource is already set up. + } + TypedArray state_meshes = p_state->get_meshes(); + ERR_FAIL_INDEX_MSG(collider_mesh_index, state_meshes.size(), "GLTF Physics: When importing '" + p_state->get_scene_name() + "', the collider mesh index " + itos(collider_mesh_index) + " is not in the state meshes (size: " + itos(state_meshes.size()) + ")."); + Ref gltf_mesh = state_meshes[collider_mesh_index]; + ERR_FAIL_COND(gltf_mesh.is_null()); + importer_mesh = gltf_mesh->get_mesh(); + ERR_FAIL_COND(importer_mesh.is_null()); + p_collider->set_importer_mesh(importer_mesh); +} + +CollisionObject3D *_generate_collision_with_body(Ref p_state, Ref p_gltf_node, Ref p_collider, Ref p_physics_body) { + print_verbose("glTF: Creating collision for: " + p_gltf_node->get_name()); + bool is_trigger = p_collider->get_is_trigger(); + // 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. + CollisionObject3D *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. + CollisionObject3D *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(Area3D); + } else { + body = memnew(StaticBody3D); + } + CollisionShape3D *shape = p_collider->to_node(); + shape->set_name(p_gltf_node->get_name() + "Shape"); + body->add_child(shape); + return body; +} + +Node3D *GLTFDocumentExtensionPhysics::generate_scene_node(Ref p_state, Ref p_gltf_node, Node *p_scene_parent) { + Ref physics_body = p_gltf_node->get_additional_data(StringName("GLTFPhysicsBody")); + Ref collider = p_gltf_node->get_additional_data(StringName("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 Vector &p_a, const Vector &p_b) { + if (p_a.size() != p_b.size()) { + return false; + } + for (int i = 0; i < p_a.size(); i++) { + const Vector3 *a_vertices = p_a[i].vertex; + const Vector3 *b_vertices = p_b[i].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); + TypedArray state_meshes = p_state->get_meshes(); + Vector 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_importer_mesh = state_gltf_mesh->get_mesh(); + ERR_CONTINUE(state_importer_mesh.is_null()); + if (state_importer_mesh == p_mesh) { + return i; + } + if (_are_all_faces_equal(state_importer_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.instantiate(); + 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)) { + CollisionShape3D *shape = Object::cast_to(p_scene_node); + Ref collider = GLTFCollider::from_node(shape); + { + Ref importer_mesh = collider->get_importer_mesh(); + if (importer_mesh.is_valid()) { + collider->set_mesh_index(_get_or_insert_mesh_in_state(p_state, importer_mesh)); + } + } + p_gltf_node->set_additional_data(StringName("GLTFCollider"), collider); + } else if (cast_to(p_scene_node)) { + CollisionObject3D *body = Object::cast_to(p_scene_node); + p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), GLTFPhysicsBody::from_node(body)); + } +} + +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(StringName("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(StringName("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 000000000000..5c4e739dba1f --- /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) override; + Vector get_supported_extensions() override; + Error parse_node_extensions(Ref p_state, Ref p_gltf_node, Dictionary &p_extensions) override; + Node3D *generate_scene_node(Ref p_state, Ref p_gltf_node, Node *p_scene_parent) override; + // Export process. + void convert_scene_node(Ref p_state, Ref p_gltf_node, Node *p_scene_node) override; + Error export_node(Ref p_state, Ref p_gltf_node, Dictionary &r_node_json, Node *p_scene_node) override; +}; + +#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 000000000000..b7fb23d37ff0 --- /dev/null +++ b/modules/gltf/extensions/physics/gltf_physics_body.cpp @@ -0,0 +1,237 @@ +/**************************************************************************/ +/* 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_3d.h" +#include "scene/3d/vehicle_body_3d.h" + +void GLTFPhysicsBody::_bind_methods() { + ClassDB::bind_static_method("GLTFPhysicsBody", D_METHOD("from_node", "body_node"), &GLTFPhysicsBody::from_node); + ClassDB::bind_method(D_METHOD("to_node"), &GLTFPhysicsBody::to_node); + + ClassDB::bind_static_method("GLTFPhysicsBody", D_METHOD("from_dictionary", "dictionary"), &GLTFPhysicsBody::from_dictionary); + 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); + ClassDB::bind_method(D_METHOD("get_inertia"), &GLTFPhysicsBody::get_inertia); + ClassDB::bind_method(D_METHOD("set_inertia", "inertia"), &GLTFPhysicsBody::set_inertia); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "body_type"), "set_body_type", "get_body_type"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass"), "set_mass", "get_mass"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity"), "set_linear_velocity", "get_linear_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "angular_velocity"), "set_angular_velocity", "get_angular_velocity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "inertia"), "set_inertia", "get_inertia"); +} + +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; +} + +Vector3 GLTFPhysicsBody::get_inertia() const { + return inertia; +} + +void GLTFPhysicsBody::set_inertia(Vector3 p_inertia) { + inertia = p_inertia; +} + +Ref GLTFPhysicsBody::from_node(const CollisionObject3D *p_body_node) { + Ref physics_body; + physics_body.instantiate(); + ERR_FAIL_COND_V_MSG(!p_body_node, physics_body, "Tried to create a GLTFPhysicsBody from a CollisionObject3D node, but the given node was null."); + if (cast_to(p_body_node)) { + physics_body->body_type = "character"; + } else if (cast_to(p_body_node)) { + physics_body->body_type = "kinematic"; + } else if (cast_to(p_body_node)) { + const RigidBody3D *body = cast_to(p_body_node); + physics_body->mass = body->get_mass(); + physics_body->linear_velocity = body->get_linear_velocity(); + physics_body->angular_velocity = body->get_angular_velocity(); + physics_body->inertia = body->get_inertia(); + 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; +} + +CollisionObject3D *GLTFPhysicsBody::to_node() const { + if (body_type == "character") { + CharacterBody3D *body = memnew(CharacterBody3D); + return body; + } + if (body_type == "kinematic") { + AnimatableBody3D *body = memnew(AnimatableBody3D); + return body; + } + if (body_type == "vehicle") { + VehicleBody3D *body = memnew(VehicleBody3D); + body->set_mass(mass); + body->set_linear_velocity(linear_velocity); + body->set_angular_velocity(angular_velocity); + body->set_inertia(inertia); + 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); + 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::from_dictionary(const Dictionary p_dictionary) { + Ref physics_body; + physics_body.instantiate(); + ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), physics_body, "Failed to parse GLTF physics body, missing required field 'type'."); + const String &body_type = p_dictionary["type"]; + physics_body->body_type = body_type; + + if (p_dictionary.has("mass")) { + physics_body->mass = p_dictionary["mass"]; + } + 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 (p_dictionary.has("inertiaTensor")) { + const Array &arr = p_dictionary["inertiaTensor"]; + if (arr.size() == 9) { + // Only use the diagonal elements of the inertia tensor matrix (principal axes). + physics_body->set_inertia(Vector3(arr[0], arr[4], arr[8])); + } else { + ERR_PRINT("Error parsing GLTF physics body: The inertia tensor must be a 3x3 matrix (9 number array)."); + } + } + 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; + } + if (inertia != Vector3()) { + Array inertia_array; + inertia_array.resize(9); + inertia_array.fill(0.0); + inertia_array[0] = inertia.x; + inertia_array[4] = inertia.y; + inertia_array[8] = inertia.z; + d["inertiaTensor"] = inertia_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 000000000000..1562c65b2b17 --- /dev/null +++ b/modules/gltf/extensions/physics/gltf_physics_body.h @@ -0,0 +1,75 @@ +/**************************************************************************/ +/* 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_3d.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(); + Vector3 inertia = 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); + + Vector3 get_inertia() const; + void set_inertia(Vector3 p_inertia); + + static Ref from_node(const CollisionObject3D *p_body_node); + CollisionObject3D *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/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 7087c3068893..efdc3b0d2d5a 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -7467,7 +7467,7 @@ Error GLTFDocument::_parse_gltf_extensions(Ref p_state) { supported_extensions.insert(ext_supported_extensions[i]); } } - Error ret = Error::OK; + Error ret = OK; for (int i = 0; i < p_state->extensions_required.size(); i++) { if (!supported_extensions.has(p_state->extensions_required[i])) { ERR_PRINT("GLTF: Can't import file '" + p_state->filename + "', required extension '" + String(p_state->extensions_required[i]) + "' is not supported. Are you missing a GLTFDocumentExtension plugin?"); diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp index 4754c316a1ac..2f66e5c5a52f 100644 --- a/modules/gltf/register_types.cpp +++ b/modules/gltf/register_types.cpp @@ -32,6 +32,7 @@ #include "extensions/gltf_document_extension_convert_importer_mesh.h" #include "extensions/gltf_spec_gloss.h" +#include "extensions/physics/gltf_document_extension_physics.h" #include "gltf_document.h" #ifdef TOOLS_ENABLED @@ -114,12 +115,14 @@ void initialize_gltf_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(GLTFAnimation); GDREGISTER_CLASS(GLTFBufferView); GDREGISTER_CLASS(GLTFCamera); + GDREGISTER_CLASS(GLTFCollider); GDREGISTER_CLASS(GLTFDocument); GDREGISTER_CLASS(GLTFDocumentExtension); GDREGISTER_CLASS(GLTFDocumentExtensionConvertImporterMesh); GDREGISTER_CLASS(GLTFLight); GDREGISTER_CLASS(GLTFMesh); GDREGISTER_CLASS(GLTFNode); + GDREGISTER_CLASS(GLTFPhysicsBody); GDREGISTER_CLASS(GLTFSkeleton); GDREGISTER_CLASS(GLTFSkin); GDREGISTER_CLASS(GLTFSpecGloss); @@ -127,6 +130,7 @@ void initialize_gltf_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(GLTFTexture); GDREGISTER_CLASS(GLTFTextureSampler); // Register GLTFDocumentExtension classes with GLTFDocument. + GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionPhysics); bool is_editor = ::Engine::get_singleton()->is_editor_hint(); if (!is_editor) { GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionConvertImporterMesh);