1/**************************************************************************/
2/* gltf_physics_shape.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "gltf_physics_shape.h"
32
33#include "../../gltf_state.h"
34
35#include "core/math/convex_hull.h"
36#include "scene/3d/area_3d.h"
37#include "scene/resources/box_shape_3d.h"
38#include "scene/resources/capsule_shape_3d.h"
39#include "scene/resources/concave_polygon_shape_3d.h"
40#include "scene/resources/convex_polygon_shape_3d.h"
41#include "scene/resources/cylinder_shape_3d.h"
42#include "scene/resources/importer_mesh.h"
43#include "scene/resources/sphere_shape_3d.h"
44
45void GLTFPhysicsShape::_bind_methods() {
46 ClassDB::bind_static_method("GLTFPhysicsShape", D_METHOD("from_node", "shape_node"), &GLTFPhysicsShape::from_node);
47 ClassDB::bind_method(D_METHOD("to_node", "cache_shapes"), &GLTFPhysicsShape::to_node, DEFVAL(false));
48
49 ClassDB::bind_static_method("GLTFPhysicsShape", D_METHOD("from_dictionary", "dictionary"), &GLTFPhysicsShape::from_dictionary);
50 ClassDB::bind_method(D_METHOD("to_dictionary"), &GLTFPhysicsShape::to_dictionary);
51
52 ClassDB::bind_method(D_METHOD("get_shape_type"), &GLTFPhysicsShape::get_shape_type);
53 ClassDB::bind_method(D_METHOD("set_shape_type", "shape_type"), &GLTFPhysicsShape::set_shape_type);
54 ClassDB::bind_method(D_METHOD("get_size"), &GLTFPhysicsShape::get_size);
55 ClassDB::bind_method(D_METHOD("set_size", "size"), &GLTFPhysicsShape::set_size);
56 ClassDB::bind_method(D_METHOD("get_radius"), &GLTFPhysicsShape::get_radius);
57 ClassDB::bind_method(D_METHOD("set_radius", "radius"), &GLTFPhysicsShape::set_radius);
58 ClassDB::bind_method(D_METHOD("get_height"), &GLTFPhysicsShape::get_height);
59 ClassDB::bind_method(D_METHOD("set_height", "height"), &GLTFPhysicsShape::set_height);
60 ClassDB::bind_method(D_METHOD("get_is_trigger"), &GLTFPhysicsShape::get_is_trigger);
61 ClassDB::bind_method(D_METHOD("set_is_trigger", "is_trigger"), &GLTFPhysicsShape::set_is_trigger);
62 ClassDB::bind_method(D_METHOD("get_mesh_index"), &GLTFPhysicsShape::get_mesh_index);
63 ClassDB::bind_method(D_METHOD("set_mesh_index", "mesh_index"), &GLTFPhysicsShape::set_mesh_index);
64 ClassDB::bind_method(D_METHOD("get_importer_mesh"), &GLTFPhysicsShape::get_importer_mesh);
65 ClassDB::bind_method(D_METHOD("set_importer_mesh", "importer_mesh"), &GLTFPhysicsShape::set_importer_mesh);
66
67 ADD_PROPERTY(PropertyInfo(Variant::STRING, "shape_type"), "set_shape_type", "get_shape_type");
68 ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size"), "set_size", "get_size");
69 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius"), "set_radius", "get_radius");
70 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height"), "set_height", "get_height");
71 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_trigger"), "set_is_trigger", "get_is_trigger");
72 ADD_PROPERTY(PropertyInfo(Variant::INT, "mesh_index"), "set_mesh_index", "get_mesh_index");
73 ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "importer_mesh", PROPERTY_HINT_RESOURCE_TYPE, "ImporterMesh"), "set_importer_mesh", "get_importer_mesh");
74}
75
76String GLTFPhysicsShape::get_shape_type() const {
77 return shape_type;
78}
79
80void GLTFPhysicsShape::set_shape_type(String p_shape_type) {
81 shape_type = p_shape_type;
82}
83
84Vector3 GLTFPhysicsShape::get_size() const {
85 return size;
86}
87
88void GLTFPhysicsShape::set_size(Vector3 p_size) {
89 size = p_size;
90}
91
92real_t GLTFPhysicsShape::get_radius() const {
93 return radius;
94}
95
96void GLTFPhysicsShape::set_radius(real_t p_radius) {
97 radius = p_radius;
98}
99
100real_t GLTFPhysicsShape::get_height() const {
101 return height;
102}
103
104void GLTFPhysicsShape::set_height(real_t p_height) {
105 height = p_height;
106}
107
108bool GLTFPhysicsShape::get_is_trigger() const {
109 return is_trigger;
110}
111
112void GLTFPhysicsShape::set_is_trigger(bool p_is_trigger) {
113 is_trigger = p_is_trigger;
114}
115
116GLTFMeshIndex GLTFPhysicsShape::get_mesh_index() const {
117 return mesh_index;
118}
119
120void GLTFPhysicsShape::set_mesh_index(GLTFMeshIndex p_mesh_index) {
121 mesh_index = p_mesh_index;
122}
123
124Ref<ImporterMesh> GLTFPhysicsShape::get_importer_mesh() const {
125 return importer_mesh;
126}
127
128void GLTFPhysicsShape::set_importer_mesh(Ref<ImporterMesh> p_importer_mesh) {
129 importer_mesh = p_importer_mesh;
130}
131
132Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_node(const CollisionShape3D *p_collider_node) {
133 Ref<GLTFPhysicsShape> gltf_shape;
134 gltf_shape.instantiate();
135 ERR_FAIL_NULL_V_MSG(p_collider_node, gltf_shape, "Tried to create a GLTFPhysicsShape from a CollisionShape3D node, but the given node was null.");
136 Node *parent = p_collider_node->get_parent();
137 if (cast_to<const Area3D>(parent)) {
138 gltf_shape->set_is_trigger(true);
139 }
140 // All the code for working with the shape is below this comment.
141 Ref<Shape3D> shape_resource = p_collider_node->get_shape();
142 ERR_FAIL_COND_V_MSG(shape_resource.is_null(), gltf_shape, "Tried to create a GLTFPhysicsShape from a CollisionShape3D node, but the given node had a null shape.");
143 gltf_shape->_shape_cache = shape_resource;
144 if (cast_to<BoxShape3D>(shape_resource.ptr())) {
145 gltf_shape->shape_type = "box";
146 Ref<BoxShape3D> box = shape_resource;
147 gltf_shape->set_size(box->get_size());
148 } else if (cast_to<const CapsuleShape3D>(shape_resource.ptr())) {
149 gltf_shape->shape_type = "capsule";
150 Ref<CapsuleShape3D> capsule = shape_resource;
151 gltf_shape->set_radius(capsule->get_radius());
152 gltf_shape->set_height(capsule->get_height());
153 } else if (cast_to<const CylinderShape3D>(shape_resource.ptr())) {
154 gltf_shape->shape_type = "cylinder";
155 Ref<CylinderShape3D> cylinder = shape_resource;
156 gltf_shape->set_radius(cylinder->get_radius());
157 gltf_shape->set_height(cylinder->get_height());
158 } else if (cast_to<const SphereShape3D>(shape_resource.ptr())) {
159 gltf_shape->shape_type = "sphere";
160 Ref<SphereShape3D> sphere = shape_resource;
161 gltf_shape->set_radius(sphere->get_radius());
162 } else if (cast_to<const ConvexPolygonShape3D>(shape_resource.ptr())) {
163 gltf_shape->shape_type = "hull";
164 Ref<ConvexPolygonShape3D> convex = shape_resource;
165 Vector<Vector3> hull_points = convex->get_points();
166 ERR_FAIL_COND_V_MSG(hull_points.size() < 3, gltf_shape, "GLTFPhysicsShape: Convex hull has fewer points (" + itos(hull_points.size()) + ") than the minimum of 3. At least 3 points are required in order to save to GLTF, since it uses a mesh to represent convex hulls.");
167 if (hull_points.size() > 255) {
168 WARN_PRINT("GLTFPhysicsShape: Convex hull has more points (" + itos(hull_points.size()) + ") than the recommended maximum of 255. This may not load correctly in other engines.");
169 }
170 // Convert the convex hull points into an array of faces.
171 Geometry3D::MeshData md;
172 Error err = ConvexHullComputer::convex_hull(hull_points, md);
173 ERR_FAIL_COND_V_MSG(err != OK, gltf_shape, "GLTFPhysicsShape: Failed to compute convex hull.");
174 Vector<Vector3> face_vertices;
175 for (uint32_t i = 0; i < md.faces.size(); i++) {
176 uint32_t index_count = md.faces[i].indices.size();
177 for (uint32_t j = 1; j < index_count - 1; j++) {
178 face_vertices.append(hull_points[md.faces[i].indices[0]]);
179 face_vertices.append(hull_points[md.faces[i].indices[j]]);
180 face_vertices.append(hull_points[md.faces[i].indices[j + 1]]);
181 }
182 }
183 // Create an ImporterMesh from the faces.
184 Ref<ImporterMesh> importer_mesh;
185 importer_mesh.instantiate();
186 Array surface_array;
187 surface_array.resize(Mesh::ArrayType::ARRAY_MAX);
188 surface_array[Mesh::ArrayType::ARRAY_VERTEX] = face_vertices;
189 importer_mesh->add_surface(Mesh::PRIMITIVE_TRIANGLES, surface_array);
190 gltf_shape->set_importer_mesh(importer_mesh);
191 } else if (cast_to<const ConcavePolygonShape3D>(shape_resource.ptr())) {
192 gltf_shape->shape_type = "trimesh";
193 Ref<ConcavePolygonShape3D> concave = shape_resource;
194 Ref<ImporterMesh> importer_mesh;
195 importer_mesh.instantiate();
196 Array surface_array;
197 surface_array.resize(Mesh::ArrayType::ARRAY_MAX);
198 surface_array[Mesh::ArrayType::ARRAY_VERTEX] = concave->get_faces();
199 importer_mesh->add_surface(Mesh::PRIMITIVE_TRIANGLES, surface_array);
200 gltf_shape->set_importer_mesh(importer_mesh);
201 } else {
202 ERR_PRINT("Tried to create a GLTFPhysicsShape from a CollisionShape3D node, but the given node's shape '" + String(Variant(shape_resource)) +
203 "' had an unsupported shape type. Only BoxShape3D, CapsuleShape3D, CylinderShape3D, SphereShape3D, ConcavePolygonShape3D, and ConvexPolygonShape3D are supported.");
204 }
205 return gltf_shape;
206}
207
208CollisionShape3D *GLTFPhysicsShape::to_node(bool p_cache_shapes) {
209 CollisionShape3D *gltf_shape = memnew(CollisionShape3D);
210 if (!p_cache_shapes || _shape_cache == nullptr) {
211 if (shape_type == "box") {
212 Ref<BoxShape3D> box;
213 box.instantiate();
214 box->set_size(size);
215 _shape_cache = box;
216 } else if (shape_type == "capsule") {
217 Ref<CapsuleShape3D> capsule;
218 capsule.instantiate();
219 capsule->set_radius(radius);
220 capsule->set_height(height);
221 _shape_cache = capsule;
222 } else if (shape_type == "cylinder") {
223 Ref<CylinderShape3D> cylinder;
224 cylinder.instantiate();
225 cylinder->set_radius(radius);
226 cylinder->set_height(height);
227 _shape_cache = cylinder;
228 } else if (shape_type == "sphere") {
229 Ref<SphereShape3D> sphere;
230 sphere.instantiate();
231 sphere->set_radius(radius);
232 _shape_cache = sphere;
233 } else if (shape_type == "hull") {
234 ERR_FAIL_COND_V_MSG(importer_mesh.is_null(), gltf_shape, "GLTFPhysicsShape: Error converting convex hull shape to a node: The mesh resource is null.");
235 Ref<ConvexPolygonShape3D> convex = importer_mesh->get_mesh()->create_convex_shape();
236 _shape_cache = convex;
237 } else if (shape_type == "trimesh") {
238 ERR_FAIL_COND_V_MSG(importer_mesh.is_null(), gltf_shape, "GLTFPhysicsShape: Error converting concave mesh shape to a node: The mesh resource is null.");
239 Ref<ConcavePolygonShape3D> concave = importer_mesh->create_trimesh_shape();
240 _shape_cache = concave;
241 } else {
242 ERR_PRINT("GLTFPhysicsShape: Error converting to a node: Shape type '" + shape_type + "' is unknown.");
243 }
244 }
245 gltf_shape->set_shape(_shape_cache);
246 return gltf_shape;
247}
248
249Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_dictionary(const Dictionary p_dictionary) {
250 ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFPhysicsShape>(), "Failed to parse GLTFPhysicsShape, missing required field 'type'.");
251 Ref<GLTFPhysicsShape> gltf_shape;
252 gltf_shape.instantiate();
253 const String &shape_type = p_dictionary["type"];
254 gltf_shape->shape_type = shape_type;
255 if (shape_type != "box" && shape_type != "capsule" && shape_type != "cylinder" && shape_type != "sphere" && shape_type != "hull" && shape_type != "trimesh") {
256 ERR_PRINT("GLTFPhysicsShape: Error parsing unknown shape type '" + shape_type + "'. Only box, capsule, cylinder, sphere, hull, and trimesh are supported.");
257 }
258 if (p_dictionary.has("radius")) {
259 gltf_shape->set_radius(p_dictionary["radius"]);
260 }
261 if (p_dictionary.has("height")) {
262 gltf_shape->set_height(p_dictionary["height"]);
263 }
264 if (p_dictionary.has("size")) {
265 const Array &arr = p_dictionary["size"];
266 if (arr.size() == 3) {
267 gltf_shape->set_size(Vector3(arr[0], arr[1], arr[2]));
268 } else {
269 ERR_PRINT("GLTFPhysicsShape: Error parsing the size, it must have exactly 3 numbers.");
270 }
271 }
272 if (p_dictionary.has("isTrigger")) {
273 gltf_shape->set_is_trigger(p_dictionary["isTrigger"]);
274 }
275 if (p_dictionary.has("mesh")) {
276 gltf_shape->set_mesh_index(p_dictionary["mesh"]);
277 }
278 if (unlikely(gltf_shape->get_mesh_index() < 0 && (shape_type == "hull" || shape_type == "trimesh"))) {
279 ERR_PRINT("Error parsing GLTFPhysicsShape: The mesh-based shape type '" + shape_type + "' does not have a valid mesh index.");
280 }
281 return gltf_shape;
282}
283
284Dictionary GLTFPhysicsShape::to_dictionary() const {
285 Dictionary d;
286 d["type"] = shape_type;
287 if (shape_type == "box") {
288 Array size_array;
289 size_array.resize(3);
290 size_array[0] = size.x;
291 size_array[1] = size.y;
292 size_array[2] = size.z;
293 d["size"] = size_array;
294 } else if (shape_type == "capsule") {
295 d["radius"] = get_radius();
296 d["height"] = get_height();
297 } else if (shape_type == "cylinder") {
298 d["radius"] = get_radius();
299 d["height"] = get_height();
300 } else if (shape_type == "sphere") {
301 d["radius"] = get_radius();
302 } else if (shape_type == "trimesh" || shape_type == "hull") {
303 d["mesh"] = get_mesh_index();
304 }
305 if (is_trigger) {
306 d["isTrigger"] = is_trigger;
307 }
308 return d;
309}
310