| 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 | |