[3.x] Implement physics support in the GLTF module

This commit is contained in:
Aaron Franke 2022-12-20 18:39:30 -06:00
parent 15c729e1d9
commit 571e4189fd
No known key found for this signature in database
GPG key ID: 40A1750B977E56BF
14 changed files with 1125 additions and 0 deletions

View file

@ -13,11 +13,13 @@ def get_doc_classes():
"GLTFAnimation", "GLTFAnimation",
"GLTFBufferView", "GLTFBufferView",
"GLTFCamera", "GLTFCamera",
"GLTFCollider",
"GLTFDocument", "GLTFDocument",
"GLTFDocumentExtension", "GLTFDocumentExtension",
"GLTFLight", "GLTFLight",
"GLTFMesh", "GLTFMesh",
"GLTFNode", "GLTFNode",
"GLTFPhysicsBody",
"GLTFSkeleton", "GLTFSkeleton",
"GLTFSkin", "GLTFSkin",
"GLTFSpecGloss", "GLTFSpecGloss",

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFCollider" inherits="Resource" version="3.6" 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="to_dictionary" qualifiers="const">
<return type="Dictionary" />
<description>
Serializes this GLTFCollider instance into a [Dictionary].
</description>
</method>
<method name="to_node">
<return type="CollisionShape" />
<argument index="0" name="cache_shapes" type="bool" default="false" />
<description>
Converts this GLTFCollider instance into a Godot [CollisionShape] node.
</description>
</method>
</methods>
<members>
<member name="array_mesh" type="ArrayMesh" setter="set_array_mesh" getter="get_array_mesh">
The [ArrayMesh] resource of the collider. This is only used when the collider type is "hull" (convex hull) or "trimesh" (concave trimesh).
</member>
<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="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>
<constants>
</constants>
</class>

View file

@ -31,30 +31,43 @@
</methods> </methods>
<members> <members>
<member name="camera" type="int" setter="set_camera" getter="get_camera" default="-1"> <member name="camera" type="int" setter="set_camera" getter="get_camera" default="-1">
If this GLTF node is a camera, the index of the [GLTFCamera] in the [GLTFState] that describes the camera's properties. If -1, this node is not a camera.
</member> </member>
<member name="children" type="PoolIntArray" setter="set_children" getter="get_children" default="PoolIntArray( )"> <member name="children" type="PoolIntArray" setter="set_children" getter="get_children" default="PoolIntArray( )">
The indices of the children nodes in the [GLTFState]. If this GLTF node has no children, this will be an empty array.
</member> </member>
<member name="height" type="int" setter="set_height" getter="get_height" default="-1"> <member name="height" type="int" setter="set_height" getter="get_height" default="-1">
How deep into the node hierarchy this node is. A root node will have a height of 0, its children will have a height of 1, and so on. If -1, the height has not been calculated.
</member> </member>
<member name="joint" type="bool" setter="set_joint" getter="get_joint" default="false"> <member name="joint" type="bool" setter="set_joint" getter="get_joint" default="false">
This property is unused and does nothing.
</member> </member>
<member name="light" type="int" setter="set_light" getter="get_light" default="-1"> <member name="light" type="int" setter="set_light" getter="get_light" default="-1">
If this GLTF node is a light, the index of the [GLTFLight] in the [GLTFState] that describes the light's properties. If -1, this node is not a light.
</member> </member>
<member name="mesh" type="int" setter="set_mesh" getter="get_mesh" default="-1"> <member name="mesh" type="int" setter="set_mesh" getter="get_mesh" default="-1">
If this GLTF node is a mesh, the index of the [GLTFMesh] in the [GLTFState] that describes the mesh's properties. If -1, this node is not a mesh.
</member> </member>
<member name="parent" type="int" setter="set_parent" getter="get_parent" default="-1"> <member name="parent" type="int" setter="set_parent" getter="get_parent" default="-1">
The index of the parent node in the [GLTFState]. If -1, this node is a root node.
</member> </member>
<member name="rotation" type="Quat" setter="set_rotation" getter="get_rotation" default="Quat( 0, 0, 0, 1 )"> <member name="rotation" type="Quat" setter="set_rotation" getter="get_rotation" default="Quat( 0, 0, 0, 1 )">
The rotation of the GLTF node relative to its parent.
</member> </member>
<member name="scale" type="Vector3" setter="set_scale" getter="get_scale" default="Vector3( 1, 1, 1 )"> <member name="scale" type="Vector3" setter="set_scale" getter="get_scale" default="Vector3( 1, 1, 1 )">
The scale of the GLTF node relative to its parent.
</member> </member>
<member name="skeleton" type="int" setter="set_skeleton" getter="get_skeleton" default="-1"> <member name="skeleton" type="int" setter="set_skeleton" getter="get_skeleton" default="-1">
If this GLTF node has a skeleton, the index of the [GLTFSkeleton] in the [GLTFState] that describes the skeleton's properties. If -1, this node does not have a skeleton.
</member> </member>
<member name="skin" type="int" setter="set_skin" getter="get_skin" default="-1"> <member name="skin" type="int" setter="set_skin" getter="get_skin" default="-1">
If this GLTF node has a skin, the index of the [GLTFSkin] in the [GLTFState] that describes the skin's properties. If -1, this node does not have a skin.
</member> </member>
<member name="translation" type="Vector3" setter="set_translation" getter="get_translation" default="Vector3( 0, 0, 0 )"> <member name="translation" type="Vector3" setter="set_translation" getter="get_translation" default="Vector3( 0, 0, 0 )">
The position of the GLTF node relative to its parent.
</member> </member>
<member name="xform" type="Transform" setter="set_xform" getter="get_xform" default="Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 )"> <member name="xform" type="Transform" setter="set_xform" getter="get_xform" default="Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 )">
The transform of the GLTF node relative to its parent. This property is usually unused since the position, rotation, and scale properties are preferred.
</member> </member>
</members> </members>
<constants> <constants>

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFPhysicsBody" inherits="Resource" version="3.6" 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="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="CollisionObject" />
<description>
Converts this GLTFPhysicsBody instance into a Godot [CollisionObject] 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="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>
<constants>
</constants>
</class>

View file

@ -6,19 +6,25 @@
[b]Note:[/b] This class is only compiled in editor builds. Run-time glTF loading and saving is [i]not[/i] available in exported projects. References to [GLTFSpecGloss] within a script will cause an error in an exported project. [b]Note:[/b] This class is only compiled in editor builds. Run-time glTF loading and saving is [i]not[/i] available in exported projects. References to [GLTFSpecGloss] within a script will cause an error in an exported project.
</description> </description>
<tutorials> <tutorials>
<link title="KHR_materials_pbrSpecularGlossiness GLTF extension spec">https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness</link>
</tutorials> </tutorials>
<methods> <methods>
</methods> </methods>
<members> <members>
<member name="diffuse_factor" type="Color" setter="set_diffuse_factor" getter="get_diffuse_factor" default="Color( 1, 1, 1, 1 )"> <member name="diffuse_factor" type="Color" setter="set_diffuse_factor" getter="get_diffuse_factor" default="Color( 1, 1, 1, 1 )">
The reflected diffuse factor of the material.
</member> </member>
<member name="diffuse_img" type="Image" setter="set_diffuse_img" getter="get_diffuse_img"> <member name="diffuse_img" type="Image" setter="set_diffuse_img" getter="get_diffuse_img">
The diffuse texture.
</member> </member>
<member name="gloss_factor" type="float" setter="set_gloss_factor" getter="get_gloss_factor" default="1.0"> <member name="gloss_factor" type="float" setter="set_gloss_factor" getter="get_gloss_factor" default="1.0">
The glossiness or smoothness of the material.
</member> </member>
<member name="spec_gloss_img" type="Image" setter="set_spec_gloss_img" getter="get_spec_gloss_img"> <member name="spec_gloss_img" type="Image" setter="set_spec_gloss_img" getter="get_spec_gloss_img">
The specular-glossiness texture.
</member> </member>
<member name="specular_factor" type="Color" setter="set_specular_factor" getter="get_specular_factor" default="Color( 1, 1, 1, 1 )"> <member name="specular_factor" type="Color" setter="set_specular_factor" getter="get_specular_factor" default="Color( 1, 1, 1, 1 )">
The specular RGB color of the material. The alpha channel is unused.
</member> </member>
</members> </members>
<constants> <constants>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<class name="GLTFState" inherits="Resource" version="3.6" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <class name="GLTFState" inherits="Resource" version="3.6" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description> <brief_description>
Represents all data of a GLTF file.
</brief_description> </brief_description>
<description> <description>
[b]Note:[/b] This class is only compiled in editor builds. Run-time glTF loading and saving is [i]not[/i] available in exported projects. References to [GLTFState] within a script will cause an error in an exported project. [b]Note:[/b] This class is only compiled in editor builds. Run-time glTF loading and saving is [i]not[/i] available in exported projects. References to [GLTFState] within a script will cause an error in an exported project.
@ -44,6 +45,7 @@
<method name="get_animations"> <method name="get_animations">
<return type="Array" /> <return type="Array" />
<description> <description>
Returns an array of all [GLTFAnimation]s in the GLTF file. When importing, these will be generated as animations in an [AnimationPlayer] node. When exporting, these will be generated from Godot [AnimationPlayer] nodes.
</description> </description>
</method> </method>
<method name="get_buffer_views"> <method name="get_buffer_views">
@ -54,6 +56,7 @@
<method name="get_cameras"> <method name="get_cameras">
<return type="Array" /> <return type="Array" />
<description> <description>
Returns an array of all [GLTFCamera]s in the GLTF file. These are the cameras that the [member GLTFNode.camera] index refers to.
</description> </description>
</method> </method>
<method name="get_images"> <method name="get_images">
@ -64,6 +67,7 @@
<method name="get_lights"> <method name="get_lights">
<return type="Array" /> <return type="Array" />
<description> <description>
Returns an array of all [GLTFLight]s in the GLTF file. These are the lights that the [member GLTFNode.light] index refers to.
</description> </description>
</method> </method>
<method name="get_materials"> <method name="get_materials">
@ -74,11 +78,13 @@
<method name="get_meshes"> <method name="get_meshes">
<return type="Array" /> <return type="Array" />
<description> <description>
Returns an array of all [GLTFMesh]es in the GLTF file. These are the meshes that the [member GLTFNode.mesh] index refers to.
</description> </description>
</method> </method>
<method name="get_nodes"> <method name="get_nodes">
<return type="Array" /> <return type="Array" />
<description> <description>
Returns an array of all [GLTFNode]s in the GLTF file. These are the nodes that [member GLTFNode.children] and [member root_nodes] refer to. This includes nodes that may not be generated in the Godot scene, or nodes that may generate multiple Godot scene nodes.
</description> </description>
</method> </method>
<method name="get_scene_node"> <method name="get_scene_node">
@ -95,11 +101,13 @@
<method name="get_skeletons"> <method name="get_skeletons">
<return type="Array" /> <return type="Array" />
<description> <description>
Returns an array of all [GLTFSkeleton]s in the GLTF file. These are the skeletons that the [member GLTFNode.skeleton] index refers to.
</description> </description>
</method> </method>
<method name="get_skins"> <method name="get_skins">
<return type="Array" /> <return type="Array" />
<description> <description>
Returns an array of all [GLTFSkin]s in the GLTF file. These are the skins that the [member GLTFNode.skin] index refers to.
</description> </description>
</method> </method>
<method name="get_texture_samplers"> <method name="get_texture_samplers">
@ -116,11 +124,13 @@
<method name="get_unique_animation_names"> <method name="get_unique_animation_names">
<return type="Array" /> <return type="Array" />
<description> <description>
Returns an array of unique animation names. This is only used during the import process.
</description> </description>
</method> </method>
<method name="get_unique_names"> <method name="get_unique_names">
<return type="Array" /> <return type="Array" />
<description> <description>
Returns an array of unique node names. This is used in both the import process and export process.
</description> </description>
</method> </method>
<method name="set_accessors"> <method name="set_accessors">
@ -244,8 +254,10 @@
<member name="minor_version" type="int" setter="set_minor_version" getter="get_minor_version" default="0"> <member name="minor_version" type="int" setter="set_minor_version" getter="get_minor_version" default="0">
</member> </member>
<member name="root_nodes" type="Array" setter="set_root_nodes" getter="get_root_nodes" default="[ ]"> <member name="root_nodes" type="Array" setter="set_root_nodes" getter="get_root_nodes" default="[ ]">
The root nodes of the GLTF file. Typically, a GLTF file will only have one scene, and therefore one root node. However, a GLTF file may have multiple scenes and therefore multiple root nodes, which will be generated as siblings of each other and as children of the root node of the generated Godot scene.
</member> </member>
<member name="scene_name" type="String" setter="set_scene_name" getter="get_scene_name" default="&quot;&quot;"> <member name="scene_name" type="String" setter="set_scene_name" getter="get_scene_name" default="&quot;&quot;">
The name of the scene. When importing, if not specified, this will be the file name. When exporting, if specified, the scene name will be saved to the GLTF file.
</member> </member>
<member name="use_named_skin_binds" type="bool" setter="set_use_named_skin_binds" getter="get_use_named_skin_binds" default="false"> <member name="use_named_skin_binds" type="bool" setter="set_use_named_skin_binds" getter="get_use_named_skin_binds" default="false">
</member> </member>

View file

@ -7,3 +7,4 @@ env_gltf = env_modules.Clone()
# Godot source files # Godot source files
env_gltf.add_source_files(env.modules_sources, "*.cpp") env_gltf.add_source_files(env.modules_sources, "*.cpp")
env_gltf.add_source_files(env.modules_sources, "physics/*.cpp")

View file

@ -0,0 +1,304 @@
/**************************************************************************/
/* gltf_collider.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "gltf_collider.h"
#include "../../gltf_state.h"
#include "core/math/convex_hull.h"
#include "scene/3d/area.h"
#include "scene/resources/box_shape.h"
#include "scene/resources/capsule_shape.h"
#include "scene/resources/concave_polygon_shape.h"
#include "scene/resources/convex_polygon_shape.h"
#include "scene/resources/cylinder_shape.h"
#include "scene/resources/sphere_shape.h"
void GLTFCollider::_bind_methods() {
ClassDB::bind_method(D_METHOD("to_node", "cache_shapes"), &GLTFCollider::to_node, DEFVAL(false));
ClassDB::bind_method(D_METHOD("to_dictionary"), &GLTFCollider::to_dictionary);
ClassDB::bind_method(D_METHOD("get_shape_type"), &GLTFCollider::get_shape_type);
ClassDB::bind_method(D_METHOD("set_shape_type", "shape_type"), &GLTFCollider::set_shape_type);
ClassDB::bind_method(D_METHOD("get_size"), &GLTFCollider::get_size);
ClassDB::bind_method(D_METHOD("set_size", "size"), &GLTFCollider::set_size);
ClassDB::bind_method(D_METHOD("get_radius"), &GLTFCollider::get_radius);
ClassDB::bind_method(D_METHOD("set_radius", "radius"), &GLTFCollider::set_radius);
ClassDB::bind_method(D_METHOD("get_height"), &GLTFCollider::get_height);
ClassDB::bind_method(D_METHOD("set_height", "height"), &GLTFCollider::set_height);
ClassDB::bind_method(D_METHOD("get_is_trigger"), &GLTFCollider::get_is_trigger);
ClassDB::bind_method(D_METHOD("set_is_trigger", "is_trigger"), &GLTFCollider::set_is_trigger);
ClassDB::bind_method(D_METHOD("get_mesh_index"), &GLTFCollider::get_mesh_index);
ClassDB::bind_method(D_METHOD("set_mesh_index", "mesh_index"), &GLTFCollider::set_mesh_index);
ClassDB::bind_method(D_METHOD("get_array_mesh"), &GLTFCollider::get_array_mesh);
ClassDB::bind_method(D_METHOD("set_array_mesh", "array_mesh"), &GLTFCollider::set_array_mesh);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "shape_type"), "set_shape_type", "get_shape_type");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size"), "set_size", "get_size");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "radius"), "set_radius", "get_radius");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "height"), "set_height", "get_height");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_trigger"), "set_is_trigger", "get_is_trigger");
ADD_PROPERTY(PropertyInfo(Variant::INT, "mesh_index"), "set_mesh_index", "get_mesh_index");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "array_mesh", PROPERTY_HINT_RESOURCE_TYPE, "ArrayMesh"), "set_array_mesh", "get_array_mesh");
}
String GLTFCollider::get_shape_type() const {
return shape_type;
}
void GLTFCollider::set_shape_type(String p_shape_type) {
shape_type = p_shape_type;
}
Vector3 GLTFCollider::get_size() const {
return size;
}
void GLTFCollider::set_size(Vector3 p_size) {
size = p_size;
}
real_t GLTFCollider::get_radius() const {
return radius;
}
void GLTFCollider::set_radius(real_t p_radius) {
radius = p_radius;
}
real_t GLTFCollider::get_height() const {
return height;
}
void GLTFCollider::set_height(real_t p_height) {
height = p_height;
}
bool GLTFCollider::get_is_trigger() const {
return is_trigger;
}
void GLTFCollider::set_is_trigger(bool p_is_trigger) {
is_trigger = p_is_trigger;
}
GLTFMeshIndex GLTFCollider::get_mesh_index() const {
return mesh_index;
}
void GLTFCollider::set_mesh_index(GLTFMeshIndex p_mesh_index) {
mesh_index = p_mesh_index;
}
Ref<ArrayMesh> GLTFCollider::get_array_mesh() const {
return array_mesh;
}
void GLTFCollider::set_array_mesh(Ref<ArrayMesh> p_array_mesh) {
array_mesh = p_array_mesh;
}
Ref<GLTFCollider> GLTFCollider::from_node(const CollisionShape *p_collider_node) {
Ref<GLTFCollider> collider;
collider.instance();
ERR_FAIL_NULL_V_MSG(p_collider_node, collider, "Tried to create a GLTFCollider from a CollisionShape node, but the given node was null.");
Node *parent = p_collider_node->get_parent();
if (cast_to<const Area>(parent)) {
collider->set_is_trigger(true);
}
// All the code for working with the shape is below this comment.
Ref<Shape> shape = p_collider_node->get_shape();
ERR_FAIL_COND_V_MSG(shape.is_null(), collider, "Tried to create a GLTFCollider from a CollisionShape node, but the given node had a null shape.");
collider->_shape_cache = shape;
if (cast_to<BoxShape>(shape.ptr())) {
collider->shape_type = "box";
Ref<BoxShape> box = shape;
collider->set_size(box->get_extents() * 2.0f);
} else if (cast_to<const CapsuleShape>(shape.ptr())) {
collider->shape_type = "capsule";
Ref<CapsuleShape> capsule = shape;
collider->set_radius(capsule->get_radius());
collider->set_height(capsule->get_height());
} else if (cast_to<const CylinderShape>(shape.ptr())) {
collider->shape_type = "cylinder";
Ref<CylinderShape> cylinder = shape;
collider->set_radius(cylinder->get_radius());
collider->set_height(cylinder->get_height());
} else if (cast_to<const SphereShape>(shape.ptr())) {
collider->shape_type = "sphere";
Ref<SphereShape> sphere = shape;
collider->set_radius(sphere->get_radius());
} else if (cast_to<const ConvexPolygonShape>(shape.ptr())) {
collider->shape_type = "hull";
Ref<ConvexPolygonShape> convex = shape;
PoolVector<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.
Geometry::MeshData md;
Error err = ConvexHullComputer::convex_hull(hull_points, md);
ERR_FAIL_COND_V_MSG(err != OK, collider, "GLTFCollider: Failed to compute convex hull.");
Vector<Vector3> face_vertices;
for (uint32_t i = 0; i < (uint32_t)md.faces.size(); i++) {
uint32_t index_count = md.faces[i].indices.size();
for (uint32_t j = 1; j < index_count - 1; j++) {
face_vertices.push_back(hull_points[md.faces[i].indices[0]]);
face_vertices.push_back(hull_points[md.faces[i].indices[j]]);
face_vertices.push_back(hull_points[md.faces[i].indices[j + 1]]);
}
}
// Create an ArrayMesh from the faces.
Ref<ArrayMesh> array_mesh;
array_mesh.instance();
Array surface_array;
surface_array.resize(Mesh::ArrayType::ARRAY_MAX);
surface_array[Mesh::ArrayType::ARRAY_VERTEX] = face_vertices;
array_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, surface_array);
collider->set_array_mesh(array_mesh);
} else if (cast_to<const ConcavePolygonShape>(shape.ptr())) {
collider->shape_type = "trimesh";
Ref<ConcavePolygonShape> concave = shape;
Ref<ArrayMesh> array_mesh;
array_mesh.instance();
Array surface_array;
surface_array.resize(Mesh::ArrayType::ARRAY_MAX);
surface_array[Mesh::ArrayType::ARRAY_VERTEX] = concave->get_faces();
array_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, surface_array);
collider->set_array_mesh(array_mesh);
} else {
ERR_PRINT("Tried to create a GLTFCollider from a CollisionShape node, but the given node's shape '" + String(Variant(shape)) +
"' had an unsupported shape type. Only BoxShape, CapsuleShape, CylinderShape, SphereShape, ConcavePolygonShape, and ConvexPolygonShape are supported.");
}
return collider;
}
CollisionShape *GLTFCollider::to_node(bool p_cache_shapes) {
CollisionShape *collider = memnew(CollisionShape);
if (!p_cache_shapes || _shape_cache == nullptr) {
if (shape_type == "box") {
Ref<BoxShape> box;
box.instance();
box->set_extents(size * 0.5f);
_shape_cache = box;
} else if (shape_type == "capsule") {
Ref<CapsuleShape> capsule;
capsule.instance();
capsule->set_radius(radius);
capsule->set_height(height);
_shape_cache = capsule;
} else if (shape_type == "cylinder") {
Ref<CylinderShape> cylinder;
cylinder.instance();
cylinder->set_radius(radius);
cylinder->set_height(height);
_shape_cache = cylinder;
} else if (shape_type == "sphere") {
Ref<SphereShape> sphere;
sphere.instance();
sphere->set_radius(radius);
_shape_cache = sphere;
} else if (shape_type == "hull") {
ERR_FAIL_COND_V_MSG(array_mesh.is_null(), collider, "GLTFCollider: Error converting convex hull collider to a node: The mesh resource is null.");
Ref<ConvexPolygonShape> convex = array_mesh->create_convex_shape();
_shape_cache = convex;
} else if (shape_type == "trimesh") {
ERR_FAIL_COND_V_MSG(array_mesh.is_null(), collider, "GLTFCollider: Error converting concave mesh collider to a node: The mesh resource is null.");
Ref<ConcavePolygonShape> concave = array_mesh->create_trimesh_shape();
_shape_cache = concave;
} else {
ERR_PRINT("GLTFCollider: Error converting to a node: Shape type '" + shape_type + "' is unknown.");
}
}
collider->set_shape(_shape_cache);
return collider;
}
Ref<GLTFCollider> 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.instance();
const String &shape_type = p_dictionary["type"];
collider->shape_type = shape_type;
if (shape_type != "box" && shape_type != "capsule" && shape_type != "cylinder" && shape_type != "sphere" && shape_type != "hull" && shape_type != "trimesh") {
ERR_PRINT("Error parsing GLTF collider: Shape type '" + shape_type + "' is unknown. Only box, capsule, cylinder, sphere, hull, and trimesh are supported.");
}
if (p_dictionary.has("radius")) {
collider->set_radius(p_dictionary["radius"]);
}
if (p_dictionary.has("height")) {
collider->set_height(p_dictionary["height"]);
}
if (p_dictionary.has("size")) {
const Array &arr = p_dictionary["size"];
if (arr.size() == 3) {
collider->set_size(Vector3(arr[0], arr[1], arr[2]));
} else {
ERR_PRINT("Error parsing GLTF collider: The size must have exactly 3 numbers.");
}
}
if (p_dictionary.has("isTrigger")) {
collider->set_is_trigger(p_dictionary["isTrigger"]);
}
if (p_dictionary.has("mesh")) {
collider->set_mesh_index(p_dictionary["mesh"]);
}
if (unlikely(collider->get_mesh_index() < 0 && (shape_type == "hull" || shape_type == "trimesh"))) {
ERR_PRINT("Error parsing GLTF collider: The mesh-based shape type '" + shape_type + "' does not have a valid mesh index.");
}
return collider;
}
Dictionary GLTFCollider::to_dictionary() const {
Dictionary d;
d["type"] = shape_type;
if (shape_type == "box") {
Array size_array;
size_array.resize(3);
size_array[0] = size.x;
size_array[1] = size.y;
size_array[2] = size.z;
d["size"] = size_array;
} else if (shape_type == "capsule") {
d["radius"] = get_radius();
d["height"] = get_height();
} else if (shape_type == "cylinder") {
d["radius"] = get_radius();
d["height"] = get_height();
} else if (shape_type == "sphere") {
d["radius"] = get_radius();
} else if (shape_type == "trimesh" || shape_type == "hull") {
d["mesh"] = get_mesh_index();
}
if (is_trigger) {
d["isTrigger"] = is_trigger;
}
return d;
}

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.h"
class GLTFState;
// GLTFCollider is an intermediary between OMI_collider and Godot's collision shape nodes.
// https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_collider
class GLTFCollider : public Resource {
GDCLASS(GLTFCollider, Resource)
protected:
static void _bind_methods();
private:
String shape_type;
Vector3 size = Vector3(1.0, 1.0, 1.0);
real_t radius = 0.5;
real_t height = 2.0;
bool is_trigger = false;
GLTFMeshIndex mesh_index = -1;
Ref<ArrayMesh> array_mesh = nullptr;
// Internal only, for caching Godot shape resources. Used in `to_node`.
Ref<Shape> _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<ArrayMesh> get_array_mesh() const;
void set_array_mesh(Ref<ArrayMesh> p_array_mesh);
static Ref<GLTFCollider> from_node(const CollisionShape *p_collider_node);
CollisionShape *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,274 @@
/**************************************************************************/
/* gltf_document_extension_physics.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "gltf_document_extension_physics.h"
#include "scene/3d/area.h"
// Import process.
Error GLTFDocumentExtensionPhysics::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) {
if (p_extensions.find("OMI_collider") < 0 && p_extensions.find("OMI_physics_body") < 0) {
return ERR_SKIP;
}
Dictionary state_json = p_state->get_json();
if (state_json.has("extensions")) {
Dictionary state_extensions = state_json["extensions"];
if (state_extensions.has("OMI_collider")) {
Dictionary omi_collider_ext = state_extensions["OMI_collider"];
if (omi_collider_ext.has("colliders")) {
Array state_collider_dicts = omi_collider_ext["colliders"];
if (state_collider_dicts.size() > 0) {
Array state_colliders;
for (int i = 0; i < state_collider_dicts.size(); i++) {
state_colliders.push_back(GLTFCollider::from_dictionary(state_collider_dicts[i]));
}
p_state->set_additional_data("GLTFColliders", state_colliders);
}
}
}
}
return OK;
}
Vector<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("GLTFCollider", state_colliders[node_collider_index]);
} else {
p_gltf_node->set_additional_data("GLTFCollider", GLTFCollider::from_dictionary(p_extensions["OMI_collider"]));
}
}
if (p_extensions.has("OMI_physics_body")) {
p_gltf_node->set_additional_data("GLTFPhysicsBody", GLTFPhysicsBody::from_dictionary(p_extensions["OMI_physics_body"]));
}
return OK;
}
void _setup_collider_mesh_resource_from_index_if_needed(Ref<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<ArrayMesh> array_mesh = p_collider->get_array_mesh();
if (array_mesh.is_valid()) {
return; // The mesh resource is already set up.
}
Array state_meshes = p_state->get_meshes();
ERR_FAIL_INDEX_MSG(collider_mesh_index, state_meshes.size(), "GLTF Physics: When importing '" + p_state->get_scene_name() + "', the collider mesh index " + itos(collider_mesh_index) + " is not in the state meshes (size: " + itos(state_meshes.size()) + ").");
Ref<GLTFMesh> gltf_mesh = state_meshes[collider_mesh_index];
ERR_FAIL_COND(gltf_mesh.is_null());
array_mesh = gltf_mesh->get_mesh();
ERR_FAIL_COND(array_mesh.is_null());
p_collider->set_array_mesh(array_mesh);
}
CollisionObject *_generate_collision_with_body(Ref<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.
CollisionObject *body;
if (p_physics_body.is_valid()) {
// This code is run when the physics body is on the same GLTF node.
body = p_physics_body->to_node();
if (is_trigger != (p_physics_body->get_body_type() == "trigger")) {
// Edge case: If the body's trigger and the collider's trigger
// are in disagreement, we need to create another new body.
CollisionObject *child = _generate_collision_with_body(p_state, p_gltf_node, p_collider, nullptr);
child->set_name(p_gltf_node->get_name() + (is_trigger ? String("Trigger") : String("Solid")));
body->add_child(child);
return body;
}
} else if (is_trigger) {
body = memnew(Area);
} else {
body = memnew(StaticBody);
}
CollisionShape *shape = p_collider->to_node();
shape->set_name(p_gltf_node->get_name() + "Shape");
body->add_child(shape);
return body;
}
Spatial *GLTFDocumentExtensionPhysics::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) {
Ref<GLTFPhysicsBody> physics_body = p_gltf_node->get_additional_data("GLTFPhysicsBody");
Ref<GLTFCollider> collider = p_gltf_node->get_additional_data("GLTFCollider");
if (collider.is_valid()) {
_setup_collider_mesh_resource_from_index_if_needed(p_state, collider);
// If the collider has the correct type of parent, we just return one node.
if (collider->get_is_trigger()) {
if (Object::cast_to<Area>(p_scene_parent)) {
return collider->to_node(true);
}
} else {
if (Object::cast_to<PhysicsBody>(p_scene_parent)) {
return collider->to_node(true);
}
}
return _generate_collision_with_body(p_state, p_gltf_node, collider, physics_body);
}
if (physics_body.is_valid()) {
return physics_body->to_node();
}
return nullptr;
}
// Export process.
bool _are_all_faces_equal(const PoolVector<Face3> &p_a, const PoolVector<Face3> &p_b) {
if (p_a.size() != p_b.size()) {
return false;
}
for (int i = 0; i < p_a.size(); i++) {
Face3 a_face = p_a[i];
Face3 b_face = p_b[i];
const Vector3 *a_vertices = a_face.vertex;
const Vector3 *b_vertices = b_face.vertex;
for (int j = 0; j < 3; j++) {
if (!a_vertices[j].is_equal_approx(b_vertices[j])) {
return false;
}
}
}
return true;
}
GLTFMeshIndex _get_or_insert_mesh_in_state(Ref<GLTFState> p_state, Ref<ArrayMesh> p_mesh) {
ERR_FAIL_COND_V(p_mesh.is_null(), -1);
Array state_meshes = p_state->get_meshes();
PoolVector<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<ArrayMesh> state_array_mesh = state_gltf_mesh->get_mesh();
ERR_CONTINUE(state_array_mesh.is_null());
if (state_array_mesh == p_mesh) {
return i;
}
if (_are_all_faces_equal(state_array_mesh->get_faces(), mesh_faces)) {
return i;
}
}
// After the loop, we have checked that the mesh is not equal to any of the
// meshes in the state. So we insert a new mesh into the state mesh array.
Ref<GLTFMesh> gltf_mesh;
gltf_mesh.instance();
gltf_mesh->set_mesh(p_mesh);
GLTFMeshIndex mesh_index = state_meshes.size();
state_meshes.push_back(gltf_mesh);
p_state->set_meshes(state_meshes);
return mesh_index;
}
void GLTFDocumentExtensionPhysics::convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node) {
if (cast_to<CollisionShape>(p_scene_node)) {
CollisionShape *shape = Object::cast_to<CollisionShape>(p_scene_node);
Ref<GLTFCollider> collider = GLTFCollider::from_node(shape);
{
Ref<ArrayMesh> array_mesh = collider->get_array_mesh();
if (array_mesh.is_valid()) {
collider->set_mesh_index(_get_or_insert_mesh_in_state(p_state, array_mesh));
}
}
p_gltf_node->set_additional_data("GLTFCollider", collider);
} else if (cast_to<CollisionObject>(p_scene_node)) {
CollisionObject *body = Object::cast_to<CollisionObject>(p_scene_node);
p_gltf_node->set_additional_data("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("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("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);
Vector<String> get_supported_extensions();
Error parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions);
Spatial *generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent);
// Export process.
void convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node);
Error export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_node_json, Node *p_scene_node);
};
#endif // GLTF_DOCUMENT_EXTENSION_PHYSICS_H

View file

@ -0,0 +1,196 @@
/**************************************************************************/
/* gltf_physics_body.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "gltf_physics_body.h"
#include "scene/3d/area.h"
#include "scene/3d/vehicle_body.h"
void GLTFPhysicsBody::_bind_methods() {
ClassDB::bind_method(D_METHOD("to_node"), &GLTFPhysicsBody::to_node);
ClassDB::bind_method(D_METHOD("to_dictionary"), &GLTFPhysicsBody::to_dictionary);
ClassDB::bind_method(D_METHOD("get_body_type"), &GLTFPhysicsBody::get_body_type);
ClassDB::bind_method(D_METHOD("set_body_type", "body_type"), &GLTFPhysicsBody::set_body_type);
ClassDB::bind_method(D_METHOD("get_mass"), &GLTFPhysicsBody::get_mass);
ClassDB::bind_method(D_METHOD("set_mass", "mass"), &GLTFPhysicsBody::set_mass);
ClassDB::bind_method(D_METHOD("get_linear_velocity"), &GLTFPhysicsBody::get_linear_velocity);
ClassDB::bind_method(D_METHOD("set_linear_velocity", "linear_velocity"), &GLTFPhysicsBody::set_linear_velocity);
ClassDB::bind_method(D_METHOD("get_angular_velocity"), &GLTFPhysicsBody::get_angular_velocity);
ClassDB::bind_method(D_METHOD("set_angular_velocity", "angular_velocity"), &GLTFPhysicsBody::set_angular_velocity);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "body_type"), "set_body_type", "get_body_type");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "mass"), "set_mass", "get_mass");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "linear_velocity"), "set_linear_velocity", "get_linear_velocity");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "angular_velocity"), "set_angular_velocity", "get_angular_velocity");
}
String GLTFPhysicsBody::get_body_type() const {
return body_type;
}
void GLTFPhysicsBody::set_body_type(String p_body_type) {
body_type = p_body_type;
}
real_t GLTFPhysicsBody::get_mass() const {
return mass;
}
void GLTFPhysicsBody::set_mass(real_t p_mass) {
mass = p_mass;
}
Vector3 GLTFPhysicsBody::get_linear_velocity() const {
return linear_velocity;
}
void GLTFPhysicsBody::set_linear_velocity(Vector3 p_linear_velocity) {
linear_velocity = p_linear_velocity;
}
Vector3 GLTFPhysicsBody::get_angular_velocity() const {
return angular_velocity;
}
void GLTFPhysicsBody::set_angular_velocity(Vector3 p_angular_velocity) {
angular_velocity = p_angular_velocity;
}
Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_node(const CollisionObject *p_body_node) {
Ref<GLTFPhysicsBody> physics_body;
physics_body.instance();
ERR_FAIL_COND_V_MSG(!p_body_node, physics_body, "Tried to create a GLTFPhysicsBody from a CollisionObject node, but the given node was null.");
if (cast_to<KinematicBody>(p_body_node)) {
physics_body->body_type = "kinematic";
} else if (cast_to<RigidBody>(p_body_node)) {
const RigidBody *body = cast_to<const RigidBody>(p_body_node);
physics_body->mass = body->get_mass();
physics_body->linear_velocity = body->get_linear_velocity();
physics_body->angular_velocity = body->get_angular_velocity();
if (cast_to<VehicleBody>(p_body_node)) {
physics_body->body_type = "vehicle";
} else {
physics_body->body_type = "rigid";
}
} else if (cast_to<StaticBody>(p_body_node)) {
physics_body->body_type = "static";
} else if (cast_to<Area>(p_body_node)) {
physics_body->body_type = "trigger";
}
return physics_body;
}
CollisionObject *GLTFPhysicsBody::to_node() const {
if (body_type == "character" || body_type == "kinematic") {
KinematicBody *body = memnew(KinematicBody);
return body;
}
if (body_type == "vehicle") {
VehicleBody *body = memnew(VehicleBody);
body->set_mass(mass);
body->set_linear_velocity(linear_velocity);
body->set_angular_velocity(angular_velocity);
return body;
}
if (body_type == "rigid") {
RigidBody *body = memnew(RigidBody);
body->set_mass(mass);
body->set_linear_velocity(linear_velocity);
body->set_angular_velocity(angular_velocity);
return body;
}
if (body_type == "static") {
StaticBody *body = memnew(StaticBody);
return body;
}
if (body_type == "trigger") {
Area *body = memnew(Area);
return body;
}
ERR_FAIL_V_MSG(nullptr, "Error converting GLTFPhysicsBody to a node: Body type '" + body_type + "' is unknown.");
}
Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_dictionary) {
Ref<GLTFPhysicsBody> physics_body;
physics_body.instance();
ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), physics_body, "Failed to parse GLTF physics body, missing required field 'type'.");
const String &body_type = p_dictionary["type"];
physics_body->body_type = body_type;
if (p_dictionary.has("mass")) {
physics_body->mass = p_dictionary["mass"];
}
if (p_dictionary.has("linearVelocity")) {
const Array &arr = p_dictionary["linearVelocity"];
if (arr.size() == 3) {
physics_body->set_linear_velocity(Vector3(arr[0], arr[1], arr[2]));
} else {
ERR_PRINT("Error parsing GLTF physics body: The linear velocity vector must have exactly 3 numbers.");
}
}
if (p_dictionary.has("angularVelocity")) {
const Array &arr = p_dictionary["angularVelocity"];
if (arr.size() == 3) {
physics_body->set_angular_velocity(Vector3(arr[0], arr[1], arr[2]));
} else {
ERR_PRINT("Error parsing GLTF physics body: The angular velocity vector must have exactly 3 numbers.");
}
}
if (body_type != "character" && body_type != "kinematic" && body_type != "rigid" && body_type != "static" && body_type != "trigger" && body_type != "vehicle") {
ERR_PRINT("Error parsing GLTF physics body: Body type '" + body_type + "' is unknown.");
}
return physics_body;
}
Dictionary GLTFPhysicsBody::to_dictionary() const {
Dictionary d;
d["type"] = body_type;
if (mass != 1.0) {
d["mass"] = mass;
}
if (linear_velocity != Vector3()) {
Array velocity_array;
velocity_array.resize(3);
velocity_array[0] = linear_velocity.x;
velocity_array[1] = linear_velocity.y;
velocity_array[2] = linear_velocity.z;
d["linearVelocity"] = velocity_array;
}
if (angular_velocity != Vector3()) {
Array velocity_array;
velocity_array.resize(3);
velocity_array[0] = angular_velocity.x;
velocity_array[1] = angular_velocity.y;
velocity_array[2] = angular_velocity.z;
d["angularVelocity"] = velocity_array;
}
return d;
}

View file

@ -0,0 +1,71 @@
/**************************************************************************/
/* gltf_physics_body.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GLTF_PHYSICS_BODY_H
#define GLTF_PHYSICS_BODY_H
#include "scene/3d/physics_body.h"
// GLTFPhysicsBody is an intermediary between OMI_physics_body and Godot's physics body nodes.
// https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_body
class GLTFPhysicsBody : public Resource {
GDCLASS(GLTFPhysicsBody, Resource)
protected:
static void _bind_methods();
private:
String body_type = "static";
real_t mass = 1.0;
Vector3 linear_velocity = Vector3();
Vector3 angular_velocity = Vector3();
public:
String get_body_type() const;
void set_body_type(String p_body_type);
real_t get_mass() const;
void set_mass(real_t p_mass);
Vector3 get_linear_velocity() const;
void set_linear_velocity(Vector3 p_linear_velocity);
Vector3 get_angular_velocity() const;
void set_angular_velocity(Vector3 p_angular_velocity);
static Ref<GLTFPhysicsBody> from_node(const CollisionObject *p_body_node);
CollisionObject *to_node() const;
static Ref<GLTFPhysicsBody> from_dictionary(const Dictionary p_dictionary);
Dictionary to_dictionary() const;
};
#endif // GLTF_PHYSICS_BODY_H

View file

@ -34,6 +34,7 @@
#include "extensions/gltf_document_extension.h" #include "extensions/gltf_document_extension.h"
#include "extensions/gltf_spec_gloss.h" #include "extensions/gltf_spec_gloss.h"
#include "extensions/physics/gltf_document_extension_physics.h"
#include "gltf_state.h" #include "gltf_state.h"
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
@ -50,6 +51,11 @@ static void _editor_init() {
} }
#endif #endif
#define GLTF_REGISTER_DOCUMENT_EXTENSION(m_doc_ext_class) \
Ref<m_doc_ext_class> extension_##m_doc_ext_class; \
extension_##m_doc_ext_class.instance(); \
GLTFDocument::register_gltf_document_extension(extension_##m_doc_ext_class);
void register_gltf_types() { void register_gltf_types() {
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
ClassDB::APIType prev_api = ClassDB::get_current_api(); ClassDB::APIType prev_api = ClassDB::get_current_api();
@ -66,19 +72,23 @@ void register_gltf_types() {
ClassDB::register_class<GLTFAnimation>(); ClassDB::register_class<GLTFAnimation>();
ClassDB::register_class<GLTFBufferView>(); ClassDB::register_class<GLTFBufferView>();
ClassDB::register_class<GLTFAccessor>(); ClassDB::register_class<GLTFAccessor>();
ClassDB::register_class<GLTFCollider>();
ClassDB::register_class<GLTFTexture>(); ClassDB::register_class<GLTFTexture>();
ClassDB::register_class<GLTFTextureSampler>(); ClassDB::register_class<GLTFTextureSampler>();
ClassDB::register_class<GLTFSkeleton>(); ClassDB::register_class<GLTFSkeleton>();
ClassDB::register_class<GLTFSkin>(); ClassDB::register_class<GLTFSkin>();
ClassDB::register_class<GLTFCamera>(); ClassDB::register_class<GLTFCamera>();
ClassDB::register_class<GLTFLight>(); ClassDB::register_class<GLTFLight>();
ClassDB::register_class<GLTFPhysicsBody>();
ClassDB::register_class<GLTFState>(); ClassDB::register_class<GLTFState>();
ClassDB::register_class<GLTFDocument>(); ClassDB::register_class<GLTFDocument>();
ClassDB::register_class<GLTFDocumentExtension>(); ClassDB::register_class<GLTFDocumentExtension>();
ClassDB::register_class<PackedSceneGLTF>(); ClassDB::register_class<PackedSceneGLTF>();
GLTF_REGISTER_DOCUMENT_EXTENSION(GLTFDocumentExtensionPhysics);
} }
void unregister_gltf_types() { void unregister_gltf_types() {
GLTFDocument::unregister_all_gltf_document_extensions();
} }
#endif // _3D_DISABLED #endif // _3D_DISABLED