Merge pull request #69266 from aaronfranke/gltf-physics

Implement physics support in the GLTF module
This commit is contained in:
Rémi Verschelde 2023-04-25 09:56:58 +02:00
commit d5b8a0fc37
No known key found for this signature in database
GPG key ID: C3336907360768E1
12 changed files with 1164 additions and 1 deletions

View file

@ -15,12 +15,14 @@ def get_doc_classes():
"GLTFAnimation",
"GLTFBufferView",
"GLTFCamera",
"GLTFCollider",
"GLTFDocument",
"GLTFDocumentExtension",
"GLTFDocumentExtensionConvertImporterMesh",
"GLTFLight",
"GLTFMesh",
"GLTFNode",
"GLTFPhysicsBody",
"GLTFSkeleton",
"GLTFSkin",
"GLTFSpecGloss",

View file

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFCollider" inherits="Resource" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
Represents a GLTF collider.
</brief_description>
<description>
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.
</description>
<tutorials>
<link title="OMI_collider GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_collider</link>
</tutorials>
<methods>
<method name="from_dictionary" qualifiers="static">
<return type="GLTFCollider" />
<param index="0" name="dictionary" type="Dictionary" />
<description>
Creates a new GLTFCollider instance by parsing the given [Dictionary].
</description>
</method>
<method name="from_node" qualifiers="static">
<return type="GLTFCollider" />
<param index="0" name="collider_node" type="CollisionShape3D" />
<description>
Create a new GLTFCollider instance from the given Godot [CollisionShape3D] node.
</description>
</method>
<method name="to_dictionary" qualifiers="const">
<return type="Dictionary" />
<description>
Serializes this GLTFCollider instance into a [Dictionary].
</description>
</method>
<method name="to_node">
<return type="CollisionShape3D" />
<param index="0" name="cache_shapes" type="bool" default="false" />
<description>
Converts this GLTFCollider instance into a Godot [CollisionShape3D] node.
</description>
</method>
</methods>
<members>
<member name="height" type="float" setter="set_height" getter="get_height" default="2.0">
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.
</member>
<member name="importer_mesh" type="ImporterMesh" setter="set_importer_mesh" getter="get_importer_mesh">
The [ImporterMesh] resource of the collider. This is only used when the collider type is "hull" (convex hull) or "trimesh" (concave trimesh).
</member>
<member name="is_trigger" type="bool" setter="set_is_trigger" getter="get_is_trigger" default="false">
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.
</member>
<member name="mesh_index" type="int" setter="set_mesh_index" getter="get_mesh_index" default="-1">
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).
</member>
<member name="radius" type="float" setter="set_radius" getter="get_radius" default="0.5">
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.
</member>
<member name="shape_type" type="String" setter="set_shape_type" getter="get_shape_type" default="&quot;&quot;">
The type of shape this collider represents. Valid values are "box", "capsule", "cylinder", "sphere", "hull", and "trimesh".
</member>
<member name="size" type="Vector3" setter="set_size" getter="get_size" default="Vector3(1, 1, 1)">
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.
</member>
</members>
</class>

View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFPhysicsBody" inherits="Resource" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
Represents a GLTF physics body.
</brief_description>
<description>
Represents a physics body as defined by the [code]OMI_physics_body[/code] GLTF extension. This class is an intermediary between the GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future.
</description>
<tutorials>
<link title="OMI_physics_body GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_body</link>
</tutorials>
<methods>
<method name="from_dictionary" qualifiers="static">
<return type="GLTFPhysicsBody" />
<param index="0" name="dictionary" type="Dictionary" />
<description>
Creates a new GLTFPhysicsBody instance by parsing the given [Dictionary].
</description>
</method>
<method name="from_node" qualifiers="static">
<return type="GLTFPhysicsBody" />
<param index="0" name="body_node" type="CollisionObject3D" />
<description>
Create a new GLTFPhysicsBody instance from the given Godot [CollisionObject3D] node.
</description>
</method>
<method name="to_dictionary" qualifiers="const">
<return type="Dictionary" />
<description>
Serializes this GLTFPhysicsBody instance into a [Dictionary].
</description>
</method>
<method name="to_node" qualifiers="const">
<return type="CollisionObject3D" />
<description>
Converts this GLTFPhysicsBody instance into a Godot [CollisionObject3D] node.
</description>
</method>
</methods>
<members>
<member name="angular_velocity" type="Vector3" setter="set_angular_velocity" getter="get_angular_velocity" default="Vector3(0, 0, 0)">
The angular velocity of the physics body, in radians per second. This is only used when the body type is "rigid" or "vehicle".
</member>
<member name="body_type" type="String" setter="set_body_type" getter="get_body_type" default="&quot;static&quot;">
The type of the body. Valid values are "static", "kinematic", "character", "rigid", "vehicle", and "trigger".
</member>
<member name="inertia" type="Vector3" setter="set_inertia" getter="get_inertia" default="Vector3(0, 0, 0)">
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.
</member>
<member name="linear_velocity" type="Vector3" setter="set_linear_velocity" getter="get_linear_velocity" default="Vector3(0, 0, 0)">
The linear velocity of the physics body, in meters per second. This is only used when the body type is "rigid" or "vehicle".
</member>
<member name="mass" type="float" setter="set_mass" getter="get_mass" default="1.0">
The mass of the physics body, in kilograms. This is only used when the body type is "rigid" or "vehicle".
</member>
</members>
</class>

View file

@ -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")

View file

@ -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<ImporterMesh> GLTFCollider::get_importer_mesh() const {
return importer_mesh;
}
void GLTFCollider::set_importer_mesh(Ref<ImporterMesh> p_importer_mesh) {
importer_mesh = p_importer_mesh;
}
Ref<GLTFCollider> GLTFCollider::from_node(const CollisionShape3D *p_collider_node) {
Ref<GLTFCollider> 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<const Area3D>(parent)) {
collider->set_is_trigger(true);
}
// All the code for working with the shape is below this comment.
Ref<Shape3D> 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<BoxShape3D>(shape.ptr())) {
collider->shape_type = "box";
Ref<BoxShape3D> box = shape;
collider->set_size(box->get_size());
} else if (cast_to<const CapsuleShape3D>(shape.ptr())) {
collider->shape_type = "capsule";
Ref<CapsuleShape3D> capsule = shape;
collider->set_radius(capsule->get_radius());
collider->set_height(capsule->get_height());
} else if (cast_to<const CylinderShape3D>(shape.ptr())) {
collider->shape_type = "cylinder";
Ref<CylinderShape3D> cylinder = shape;
collider->set_radius(cylinder->get_radius());
collider->set_height(cylinder->get_height());
} else if (cast_to<const SphereShape3D>(shape.ptr())) {
collider->shape_type = "sphere";
Ref<SphereShape3D> sphere = shape;
collider->set_radius(sphere->get_radius());
} else if (cast_to<const ConvexPolygonShape3D>(shape.ptr())) {
collider->shape_type = "hull";
Ref<ConvexPolygonShape3D> convex = shape;
Vector<Vector3> 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<Vector3> 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<ImporterMesh> 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<const ConcavePolygonShape3D>(shape.ptr())) {
collider->shape_type = "trimesh";
Ref<ConcavePolygonShape3D> concave = shape;
Ref<ImporterMesh> 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<BoxShape3D> box;
box.instantiate();
box->set_size(size);
_shape_cache = box;
} else if (shape_type == "capsule") {
Ref<CapsuleShape3D> capsule;
capsule.instantiate();
capsule->set_radius(radius);
capsule->set_height(height);
_shape_cache = capsule;
} else if (shape_type == "cylinder") {
Ref<CylinderShape3D> cylinder;
cylinder.instantiate();
cylinder->set_radius(radius);
cylinder->set_height(height);
_shape_cache = cylinder;
} else if (shape_type == "sphere") {
Ref<SphereShape3D> 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<ConvexPolygonShape3D> convex = importer_mesh->get_mesh()->create_convex_shape();
_shape_cache = convex;
} else if (shape_type == "trimesh") {
ERR_FAIL_COND_V_MSG(importer_mesh.is_null(), collider, "GLTFCollider: Error converting concave mesh collider to a node: The mesh resource is null.");
Ref<ConcavePolygonShape3D> 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> GLTFCollider::from_dictionary(const Dictionary p_dictionary) {
ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFCollider>(), "Failed to parse GLTF collider, missing required field 'type'.");
Ref<GLTFCollider> 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;
}

View file

@ -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<ImporterMesh> importer_mesh = nullptr;
// Internal only, for caching Godot shape resources. Used in `to_node`.
Ref<Shape3D> _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<ImporterMesh> get_importer_mesh() const;
void set_importer_mesh(Ref<ImporterMesh> p_importer_mesh);
static Ref<GLTFCollider> from_node(const CollisionShape3D *p_collider_node);
CollisionShape3D *to_node(bool p_cache_shapes = false);
static Ref<GLTFCollider> from_dictionary(const Dictionary p_dictionary);
Dictionary to_dictionary() const;
};
#endif // GLTF_COLLIDER_H

View file

@ -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<GLTFState> p_state, Vector<String> 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<String> GLTFDocumentExtensionPhysics::get_supported_extensions() {
Vector<String> ret;
ret.push_back("OMI_collider");
ret.push_back("OMI_physics_body");
return ret;
}
Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> 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<GLTFState> p_state, Ref<GLTFCollider> p_collider) {
GLTFMeshIndex collider_mesh_index = p_collider->get_mesh_index();
if (collider_mesh_index == -1) {
return; // No mesh for this collider.
}
Ref<ImporterMesh> importer_mesh = p_collider->get_importer_mesh();
if (importer_mesh.is_valid()) {
return; // The mesh resource is already set up.
}
TypedArray<GLTFMesh> state_meshes = p_state->get_meshes();
ERR_FAIL_INDEX_MSG(collider_mesh_index, state_meshes.size(), "GLTF Physics: When importing '" + p_state->get_scene_name() + "', the collider mesh index " + itos(collider_mesh_index) + " is not in the state meshes (size: " + itos(state_meshes.size()) + ").");
Ref<GLTFMesh> gltf_mesh = state_meshes[collider_mesh_index];
ERR_FAIL_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<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Ref<GLTFCollider> p_collider, Ref<GLTFPhysicsBody> p_physics_body) {
print_verbose("glTF: Creating collision for: " + p_gltf_node->get_name());
bool is_trigger = p_collider->get_is_trigger();
// 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<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) {
Ref<GLTFPhysicsBody> physics_body = p_gltf_node->get_additional_data(StringName("GLTFPhysicsBody"));
Ref<GLTFCollider> 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<Area3D>(p_scene_parent)) {
return collider->to_node(true);
}
} else {
if (Object::cast_to<PhysicsBody3D>(p_scene_parent)) {
return collider->to_node(true);
}
}
return _generate_collision_with_body(p_state, p_gltf_node, collider, physics_body);
}
if (physics_body.is_valid()) {
return physics_body->to_node();
}
return nullptr;
}
// Export process.
bool _are_all_faces_equal(const Vector<Face3> &p_a, const Vector<Face3> &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<GLTFState> p_state, Ref<ImporterMesh> p_mesh) {
ERR_FAIL_COND_V(p_mesh.is_null(), -1);
TypedArray<GLTFMesh> state_meshes = p_state->get_meshes();
Vector<Face3> 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<GLTFMesh> state_gltf_mesh = state_meshes[i];
ERR_CONTINUE(state_gltf_mesh.is_null());
Ref<ImporterMesh> 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<GLTFMesh> 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<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node) {
if (cast_to<CollisionShape3D>(p_scene_node)) {
CollisionShape3D *shape = Object::cast_to<CollisionShape3D>(p_scene_node);
Ref<GLTFCollider> collider = GLTFCollider::from_node(shape);
{
Ref<ImporterMesh> 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<CollisionObject3D>(p_scene_node)) {
CollisionObject3D *body = Object::cast_to<CollisionObject3D>(p_scene_node);
p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody"), GLTFPhysicsBody::from_node(body));
}
}
Array _get_or_create_state_colliders_in_state(Ref<GLTFState> 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<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_node_json, Node *p_node) {
Dictionary node_extensions = r_node_json["extensions"];
Ref<GLTFPhysicsBody> physics_body = p_gltf_node->get_additional_data(StringName("GLTFPhysicsBody"));
if (physics_body.is_valid()) {
node_extensions["OMI_physics_body"] = physics_body->to_dictionary();
p_state->add_used_extension("OMI_physics_body");
}
Ref<GLTFCollider> 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;
}

View file

@ -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<GLTFState> p_state, Vector<String> p_extensions) override;
Vector<String> get_supported_extensions() override;
Error parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions) override;
Node3D *generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) override;
// Export process.
void convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node) override;
Error export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_node_json, Node *p_scene_node) override;
};
#endif // GLTF_DOCUMENT_EXTENSION_PHYSICS_H

View file

@ -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> GLTFPhysicsBody::from_node(const CollisionObject3D *p_body_node) {
Ref<GLTFPhysicsBody> 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<CharacterBody3D>(p_body_node)) {
physics_body->body_type = "character";
} else if (cast_to<AnimatableBody3D>(p_body_node)) {
physics_body->body_type = "kinematic";
} else if (cast_to<RigidBody3D>(p_body_node)) {
const RigidBody3D *body = cast_to<const RigidBody3D>(p_body_node);
physics_body->mass = body->get_mass();
physics_body->linear_velocity = body->get_linear_velocity();
physics_body->angular_velocity = body->get_angular_velocity();
physics_body->inertia = body->get_inertia();
if (cast_to<VehicleBody3D>(p_body_node)) {
physics_body->body_type = "vehicle";
} else {
physics_body->body_type = "rigid";
}
} else if (cast_to<StaticBody3D>(p_body_node)) {
physics_body->body_type = "static";
} else if (cast_to<Area3D>(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> GLTFPhysicsBody::from_dictionary(const Dictionary p_dictionary) {
Ref<GLTFPhysicsBody> physics_body;
physics_body.instantiate();
ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), physics_body, "Failed to parse GLTF physics body, missing required field 'type'.");
const String &body_type = p_dictionary["type"];
physics_body->body_type = body_type;
if (p_dictionary.has("mass")) {
physics_body->mass = p_dictionary["mass"];
}
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;
}

View file

@ -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<GLTFPhysicsBody> from_node(const CollisionObject3D *p_body_node);
CollisionObject3D *to_node() const;
static Ref<GLTFPhysicsBody> from_dictionary(const Dictionary p_dictionary);
Dictionary to_dictionary() const;
};
#endif // GLTF_PHYSICS_BODY_H

View file

@ -7467,7 +7467,7 @@ Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> 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?");

View file

@ -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);