1 | /**************************************************************************/ |
2 | /* gltf_document_extension_physics.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_document_extension_physics.h" |
32 | |
33 | #include "scene/3d/area_3d.h" |
34 | |
35 | // Import process. |
36 | Error GLTFDocumentExtensionPhysics::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) { |
37 | if (!p_extensions.has("OMI_collider" ) && !p_extensions.has("OMI_physics_body" )) { |
38 | return ERR_SKIP; |
39 | } |
40 | Dictionary state_json = p_state->get_json(); |
41 | if (state_json.has("extensions" )) { |
42 | Dictionary state_extensions = state_json["extensions" ]; |
43 | if (state_extensions.has("OMI_collider" )) { |
44 | Dictionary omi_collider_ext = state_extensions["OMI_collider" ]; |
45 | if (omi_collider_ext.has("colliders" )) { |
46 | Array state_collider_dicts = omi_collider_ext["colliders" ]; |
47 | if (state_collider_dicts.size() > 0) { |
48 | Array state_colliders; |
49 | for (int i = 0; i < state_collider_dicts.size(); i++) { |
50 | state_colliders.push_back(GLTFPhysicsShape::from_dictionary(state_collider_dicts[i])); |
51 | } |
52 | p_state->set_additional_data("GLTFPhysicsShapes" , state_colliders); |
53 | } |
54 | } |
55 | } |
56 | } |
57 | return OK; |
58 | } |
59 | |
60 | Vector<String> GLTFDocumentExtensionPhysics::get_supported_extensions() { |
61 | Vector<String> ret; |
62 | ret.push_back("OMI_collider" ); |
63 | ret.push_back("OMI_physics_body" ); |
64 | return ret; |
65 | } |
66 | |
67 | Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions) { |
68 | if (p_extensions.has("OMI_collider" )) { |
69 | Dictionary node_collider_ext = p_extensions["OMI_collider" ]; |
70 | if (node_collider_ext.has("collider" )) { |
71 | // "collider" is the index of the collider in the state colliders array. |
72 | int node_collider_index = node_collider_ext["collider" ]; |
73 | Array state_colliders = p_state->get_additional_data("GLTFPhysicsShapes" ); |
74 | 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()) + ")." ); |
75 | p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape" ), state_colliders[node_collider_index]); |
76 | } else { |
77 | p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape" ), GLTFPhysicsShape::from_dictionary(p_extensions["OMI_collider" ])); |
78 | } |
79 | } |
80 | if (p_extensions.has("OMI_physics_body" )) { |
81 | p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody" ), GLTFPhysicsBody::from_dictionary(p_extensions["OMI_physics_body" ])); |
82 | } |
83 | return OK; |
84 | } |
85 | |
86 | void _setup_collider_mesh_resource_from_index_if_needed(Ref<GLTFState> p_state, Ref<GLTFPhysicsShape> p_collider) { |
87 | GLTFMeshIndex collider_mesh_index = p_collider->get_mesh_index(); |
88 | if (collider_mesh_index == -1) { |
89 | return; // No mesh for this collider. |
90 | } |
91 | Ref<ImporterMesh> importer_mesh = p_collider->get_importer_mesh(); |
92 | if (importer_mesh.is_valid()) { |
93 | return; // The mesh resource is already set up. |
94 | } |
95 | TypedArray<GLTFMesh> state_meshes = p_state->get_meshes(); |
96 | 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()) + ")." ); |
97 | Ref<GLTFMesh> gltf_mesh = state_meshes[collider_mesh_index]; |
98 | ERR_FAIL_COND(gltf_mesh.is_null()); |
99 | importer_mesh = gltf_mesh->get_mesh(); |
100 | ERR_FAIL_COND(importer_mesh.is_null()); |
101 | p_collider->set_importer_mesh(importer_mesh); |
102 | } |
103 | |
104 | CollisionObject3D *_generate_collision_with_body(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Ref<GLTFPhysicsShape> p_collider, Ref<GLTFPhysicsBody> p_physics_body) { |
105 | print_verbose("glTF: Creating collision for: " + p_gltf_node->get_name()); |
106 | bool is_trigger = p_collider->get_is_trigger(); |
107 | // This method is used for the case where we must generate a parent body. |
108 | // This is can happen for multiple reasons. One possibility is that this |
109 | // GLTF file is using OMI_collider but not OMI_physics_body, or at least |
110 | // this particular node is not using it. Another possibility is that the |
111 | // physics body information is set up on the same GLTF node, not a parent. |
112 | CollisionObject3D *body; |
113 | if (p_physics_body.is_valid()) { |
114 | // This code is run when the physics body is on the same GLTF node. |
115 | body = p_physics_body->to_node(); |
116 | if (is_trigger != (p_physics_body->get_body_type() == "trigger" )) { |
117 | // Edge case: If the body's trigger and the collider's trigger |
118 | // are in disagreement, we need to create another new body. |
119 | CollisionObject3D *child = _generate_collision_with_body(p_state, p_gltf_node, p_collider, nullptr); |
120 | child->set_name(p_gltf_node->get_name() + (is_trigger ? String("Trigger" ) : String("Solid" ))); |
121 | body->add_child(child); |
122 | return body; |
123 | } |
124 | } else if (is_trigger) { |
125 | body = memnew(Area3D); |
126 | } else { |
127 | body = memnew(StaticBody3D); |
128 | } |
129 | CollisionShape3D *shape = p_collider->to_node(); |
130 | shape->set_name(p_gltf_node->get_name() + "Shape" ); |
131 | body->add_child(shape); |
132 | return body; |
133 | } |
134 | |
135 | Node3D *GLTFDocumentExtensionPhysics::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) { |
136 | Ref<GLTFPhysicsBody> physics_body = p_gltf_node->get_additional_data(StringName("GLTFPhysicsBody" )); |
137 | Ref<GLTFPhysicsShape> collider = p_gltf_node->get_additional_data(StringName("GLTFPhysicsShape" )); |
138 | if (collider.is_valid()) { |
139 | _setup_collider_mesh_resource_from_index_if_needed(p_state, collider); |
140 | // If the collider has the correct type of parent, we just return one node. |
141 | if (collider->get_is_trigger()) { |
142 | if (Object::cast_to<Area3D>(p_scene_parent)) { |
143 | return collider->to_node(true); |
144 | } |
145 | } else { |
146 | if (Object::cast_to<PhysicsBody3D>(p_scene_parent)) { |
147 | return collider->to_node(true); |
148 | } |
149 | } |
150 | return _generate_collision_with_body(p_state, p_gltf_node, collider, physics_body); |
151 | } |
152 | if (physics_body.is_valid()) { |
153 | return physics_body->to_node(); |
154 | } |
155 | return nullptr; |
156 | } |
157 | |
158 | // Export process. |
159 | bool _are_all_faces_equal(const Vector<Face3> &p_a, const Vector<Face3> &p_b) { |
160 | if (p_a.size() != p_b.size()) { |
161 | return false; |
162 | } |
163 | for (int i = 0; i < p_a.size(); i++) { |
164 | const Vector3 *a_vertices = p_a[i].vertex; |
165 | const Vector3 *b_vertices = p_b[i].vertex; |
166 | for (int j = 0; j < 3; j++) { |
167 | if (!a_vertices[j].is_equal_approx(b_vertices[j])) { |
168 | return false; |
169 | } |
170 | } |
171 | } |
172 | return true; |
173 | } |
174 | |
175 | GLTFMeshIndex _get_or_insert_mesh_in_state(Ref<GLTFState> p_state, Ref<ImporterMesh> p_mesh) { |
176 | ERR_FAIL_COND_V(p_mesh.is_null(), -1); |
177 | TypedArray<GLTFMesh> state_meshes = p_state->get_meshes(); |
178 | Vector<Face3> mesh_faces = p_mesh->get_faces(); |
179 | // De-duplication: If the state already has the mesh we need, use that one. |
180 | for (GLTFMeshIndex i = 0; i < state_meshes.size(); i++) { |
181 | Ref<GLTFMesh> state_gltf_mesh = state_meshes[i]; |
182 | ERR_CONTINUE(state_gltf_mesh.is_null()); |
183 | Ref<ImporterMesh> state_importer_mesh = state_gltf_mesh->get_mesh(); |
184 | ERR_CONTINUE(state_importer_mesh.is_null()); |
185 | if (state_importer_mesh == p_mesh) { |
186 | return i; |
187 | } |
188 | if (_are_all_faces_equal(state_importer_mesh->get_faces(), mesh_faces)) { |
189 | return i; |
190 | } |
191 | } |
192 | // After the loop, we have checked that the mesh is not equal to any of the |
193 | // meshes in the state. So we insert a new mesh into the state mesh array. |
194 | Ref<GLTFMesh> gltf_mesh; |
195 | gltf_mesh.instantiate(); |
196 | gltf_mesh->set_mesh(p_mesh); |
197 | GLTFMeshIndex mesh_index = state_meshes.size(); |
198 | state_meshes.push_back(gltf_mesh); |
199 | p_state->set_meshes(state_meshes); |
200 | return mesh_index; |
201 | } |
202 | |
203 | void GLTFDocumentExtensionPhysics::convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node) { |
204 | if (cast_to<CollisionShape3D>(p_scene_node)) { |
205 | CollisionShape3D *shape = Object::cast_to<CollisionShape3D>(p_scene_node); |
206 | Ref<GLTFPhysicsShape> collider = GLTFPhysicsShape::from_node(shape); |
207 | { |
208 | Ref<ImporterMesh> importer_mesh = collider->get_importer_mesh(); |
209 | if (importer_mesh.is_valid()) { |
210 | collider->set_mesh_index(_get_or_insert_mesh_in_state(p_state, importer_mesh)); |
211 | } |
212 | } |
213 | p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape" ), collider); |
214 | } else if (cast_to<CollisionObject3D>(p_scene_node)) { |
215 | CollisionObject3D *body = Object::cast_to<CollisionObject3D>(p_scene_node); |
216 | p_gltf_node->set_additional_data(StringName("GLTFPhysicsBody" ), GLTFPhysicsBody::from_node(body)); |
217 | } |
218 | } |
219 | |
220 | Array _get_or_create_state_colliders_in_state(Ref<GLTFState> p_state) { |
221 | Dictionary state_json = p_state->get_json(); |
222 | Dictionary state_extensions; |
223 | if (state_json.has("extensions" )) { |
224 | state_extensions = state_json["extensions" ]; |
225 | } else { |
226 | state_json["extensions" ] = state_extensions; |
227 | } |
228 | Dictionary omi_collider_ext; |
229 | if (state_extensions.has("OMI_collider" )) { |
230 | omi_collider_ext = state_extensions["OMI_collider" ]; |
231 | } else { |
232 | state_extensions["OMI_collider" ] = omi_collider_ext; |
233 | p_state->add_used_extension("OMI_collider" ); |
234 | } |
235 | Array state_colliders; |
236 | if (omi_collider_ext.has("colliders" )) { |
237 | state_colliders = omi_collider_ext["colliders" ]; |
238 | } else { |
239 | omi_collider_ext["colliders" ] = state_colliders; |
240 | } |
241 | return state_colliders; |
242 | } |
243 | |
244 | Error GLTFDocumentExtensionPhysics::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_node_json, Node *p_node) { |
245 | Dictionary node_extensions = r_node_json["extensions" ]; |
246 | Ref<GLTFPhysicsBody> physics_body = p_gltf_node->get_additional_data(StringName("GLTFPhysicsBody" )); |
247 | if (physics_body.is_valid()) { |
248 | node_extensions["OMI_physics_body" ] = physics_body->to_dictionary(); |
249 | p_state->add_used_extension("OMI_physics_body" ); |
250 | } |
251 | Ref<GLTFPhysicsShape> collider = p_gltf_node->get_additional_data(StringName("GLTFPhysicsShape" )); |
252 | if (collider.is_valid()) { |
253 | Array state_colliders = _get_or_create_state_colliders_in_state(p_state); |
254 | int size = state_colliders.size(); |
255 | Dictionary omi_collider_ext; |
256 | node_extensions["OMI_collider" ] = omi_collider_ext; |
257 | Dictionary collider_dict = collider->to_dictionary(); |
258 | for (int i = 0; i < size; i++) { |
259 | Dictionary other = state_colliders[i]; |
260 | if (other == collider_dict) { |
261 | // De-duplication: If we already have an identical collider, |
262 | // set the collider index to the existing one and return. |
263 | omi_collider_ext["collider" ] = i; |
264 | return OK; |
265 | } |
266 | } |
267 | // If we don't have an identical collider, add it to the array. |
268 | state_colliders.push_back(collider_dict); |
269 | omi_collider_ext["collider" ] = size; |
270 | } |
271 | return OK; |
272 | } |
273 | |