From 652ef966f99de0a915a601b686378f98c1a49b08 Mon Sep 17 00:00:00 2001 From: Lyuma Date: Sun, 25 Feb 2024 00:36:39 -0800 Subject: [PATCH] Add new scene import option to import as Skeleton Adds a bool import option `nodes/import_as_skeleton_bones`. This is supported in all FBX or GLTF document based formats. It is especially useful for retargeting and importing animations. --- doc/classes/ResourceImporterScene.xml | 3 +++ editor/import/3d/resource_importer_scene.cpp | 1 + editor/import/3d/scene_import_settings.cpp | 16 ++++++++++++++++ editor/import/3d/scene_import_settings.h | 1 + .../editor/editor_scene_importer_fbx2gltf.cpp | 3 +++ .../fbx/editor/editor_scene_importer_ufbx.cpp | 6 ++++++ modules/fbx/fbx_document.cpp | 2 +- modules/gltf/doc_classes/GLTFState.xml | 5 +++++ .../gltf/editor/editor_scene_importer_blend.cpp | 3 +++ modules/gltf/gltf_document.cpp | 2 +- modules/gltf/gltf_state.cpp | 11 +++++++++++ modules/gltf/gltf_state.h | 4 ++++ modules/gltf/skin_tool.cpp | 13 ++++++++++++- modules/gltf/skin_tool.h | 3 ++- 14 files changed, 69 insertions(+), 4 deletions(-) diff --git a/doc/classes/ResourceImporterScene.xml b/doc/classes/ResourceImporterScene.xml index 6a88adf4216..4e20fe150ea 100644 --- a/doc/classes/ResourceImporterScene.xml +++ b/doc/classes/ResourceImporterScene.xml @@ -53,6 +53,9 @@ If [code]true[/code], [member nodes/root_scale] will be applied to the descendant nodes, meshes, animations, bones, etc. This means that if you add a child node later on within the imported scene, it won't be scaled. If [code]false[/code], [member nodes/root_scale] will multiply the scale of the root node instead. + + Treat all nodes in the imported scene as if they are bones within a single [Skeleton3D]. Can be used to guarantee that imported animations target skeleton bones rather than nodes. May also be used to assign the [code]"Root"[/code] bone in a [BoneMap]. See [url=$DOCS_URL/tutorials/assets_pipeline/retargeting_3d_skeletons.html]Retargeting 3D Skeletons[/url] for more information. + Override for the root node name. If empty, the root node will use what the scene specifies, or the file name if the scene does not specify a root name. diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp index 7a51394bbc2..b7b82f5f768 100644 --- a/editor/import/3d/resource_importer_scene.cpp +++ b/editor/import/3d/resource_importer_scene.cpp @@ -1932,6 +1932,7 @@ void ResourceImporterScene::get_import_options(const String &p_path, Listpush_back(ImportOption(PropertyInfo(Variant::BOOL, "nodes/apply_root_scale"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "nodes/root_scale", PROPERTY_HINT_RANGE, "0.001,1000,0.001"), 1.0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "nodes/import_as_skeleton_bones"), false)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/ensure_tangents"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/generate_lods"), true)); r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "meshes/create_shadow_meshes"), true)); diff --git a/editor/import/3d/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp index 53d7e63dbb7..b2704245136 100644 --- a/editor/import/3d/scene_import_settings.cpp +++ b/editor/import/3d/scene_import_settings.cpp @@ -432,6 +432,16 @@ void SceneImportSettingsDialog::_update_view_gizmos() { if (!is_visible()) { return; } + const HashMap &main_settings = scene_import_settings_data->current; + if (main_settings.has("nodes/import_as_skeleton_bones")) { + bool new_import_as_skeleton = main_settings["nodes/import_as_skeleton_bones"]; + if (new_import_as_skeleton != previous_import_as_skeleton) { + previous_import_as_skeleton = new_import_as_skeleton; + _re_import(); + open_settings(base_path); + } + return; + } for (const KeyValue &e : node_map) { bool show_collider_view = false; if (e.value.settings.has(SNAME("generate/physics"))) { @@ -591,6 +601,7 @@ void SceneImportSettingsDialog::update_view() { void SceneImportSettingsDialog::open_settings(const String &p_path, bool p_for_animation) { if (scene) { + _cleanup(); memdelete(scene); scene = nullptr; } @@ -667,6 +678,10 @@ void SceneImportSettingsDialog::open_settings(const String &p_path, bool p_for_a first_aabb = false; } + const HashMap &main_settings = scene_import_settings_data->current; + if (main_settings.has("nodes/import_as_skeleton_bones")) { + previous_import_as_skeleton = main_settings["nodes/import_as_skeleton_bones"]; + } popup_centered_ratio(); _update_view_gizmos(); _update_camera(); @@ -1137,6 +1152,7 @@ void SceneImportSettingsDialog::_re_import() { main_settings["_subresources"] = subresources; } + _cleanup(); // Prevent skeletons and other pointers from pointing to dangling references. EditorFileSystem::get_singleton()->reimport_file_with_custom_parameters(base_path, editing_animation ? "animation_library" : "scene", main_settings); } diff --git a/editor/import/3d/scene_import_settings.h b/editor/import/3d/scene_import_settings.h index c6c416daba9..db3a2229f43 100644 --- a/editor/import/3d/scene_import_settings.h +++ b/editor/import/3d/scene_import_settings.h @@ -96,6 +96,7 @@ class SceneImportSettingsDialog : public ConfirmationDialog { Button *animation_stop_button = nullptr; Animation::LoopMode animation_loop_mode = Animation::LOOP_NONE; bool animation_pingpong = false; + bool previous_import_as_skeleton = false; Ref collider_mat; diff --git a/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp b/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp index 8207149d16e..3ef54ec0df8 100644 --- a/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp +++ b/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp @@ -104,6 +104,9 @@ Node *EditorSceneFormatImporterFBX2GLTF::import_scene(const String &p_path, uint gltf.instantiate(); Ref state; state.instantiate(); + if (p_options.has(SNAME("nodes/import_as_skeleton_bones")) ? (bool)p_options[SNAME("nodes/import_as_skeleton_bones")] : false) { + state->set_import_as_skeleton_bones(true); + } print_verbose(vformat("glTF path: %s", sink)); Error err = gltf->append_from_file(sink, state, p_flags, p_path.get_base_dir()); if (err != OK) { diff --git a/modules/fbx/editor/editor_scene_importer_ufbx.cpp b/modules/fbx/editor/editor_scene_importer_ufbx.cpp index 721caedc7c9..241fdba0c51 100644 --- a/modules/fbx/editor/editor_scene_importer_ufbx.cpp +++ b/modules/fbx/editor/editor_scene_importer_ufbx.cpp @@ -73,6 +73,12 @@ Node *EditorSceneFormatImporterUFBX::import_scene(const String &p_path, uint32_t int32_t enum_option = p_options["fbx/embedded_image_handling"]; state->set_handle_binary_image(enum_option); } + if (p_options.has(SNAME("nodes/import_as_skeleton_bones")) ? (bool)p_options[SNAME("nodes/import_as_skeleton_bones")] : false) { + state->set_import_as_skeleton_bones(true); + } + if (p_options.has(SNAME("nodes/import_as_skeleton_bones")) ? (bool)p_options[SNAME("nodes/import_as_skeleton_bones")] : false) { + state->set_import_as_skeleton_bones(true); + } p_flags |= EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS; Error err = fbx->append_from_file(path, state, p_flags, p_path.get_base_dir()); if (err != OK) { diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp index 2f8fd79be58..367117edcbe 100644 --- a/modules/fbx/fbx_document.cpp +++ b/modules/fbx/fbx_document.cpp @@ -2086,7 +2086,7 @@ Error FBXDocument::_parse_fbx_state(Ref p_state, const String &p_searc ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* DETERMINE SKELETONS */ - err = SkinTool::_determine_skeletons(p_state->skins, p_state->nodes, p_state->skeletons); + err = SkinTool::_determine_skeletons(p_state->skins, p_state->nodes, p_state->skeletons, p_state->get_import_as_skeleton_bones() ? p_state->root_nodes : Vector()); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* CREATE SKELETONS */ diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml index 100750f4000..6c7c5ee0e63 100644 --- a/modules/gltf/doc_classes/GLTFState.xml +++ b/modules/gltf/doc_classes/GLTFState.xml @@ -289,8 +289,13 @@ The file name associated with this GLTF data. If it ends with [code].gltf[/code], this is text-based GLTF, otherwise this is binary GLB. This will be set during import when appending from a file, and will be set during export when writing to a file. If writing to a buffer, this will be an empty string. + The binary buffer attached to a .glb file. + + + True to force all GLTFNodes in the document to be bones of a single Skeleton3D godot node. + The original raw JSON document corresponding to this GLTFState. diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index a91856c4a14..c6e92de762d 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -287,6 +287,9 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ if (p_options.has(SNAME("blender/materials/unpack_enabled")) && p_options[SNAME("blender/materials/unpack_enabled")]) { base_dir = sink.get_base_dir(); } + if (p_options.has(SNAME("nodes/import_as_skeleton_bones")) ? (bool)p_options[SNAME("nodes/import_as_skeleton_bones")] : false) { + state->set_import_as_skeleton_bones(true); + } state->set_scene_name(blend_basename); err = gltf->append_from_file(sink.get_basename() + ".gltf", state, p_flags, base_dir); if (err != OK) { diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 7949d64920d..afb3659699b 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -6924,7 +6924,7 @@ Error GLTFDocument::_parse_gltf_state(Ref p_state, const String &p_se ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* DETERMINE SKELETONS */ - err = SkinTool::_determine_skeletons(p_state->skins, p_state->nodes, p_state->skeletons); + err = SkinTool::_determine_skeletons(p_state->skins, p_state->nodes, p_state->skeletons, p_state->get_import_as_skeleton_bones() ? p_state->root_nodes : Vector()); ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR); /* PARSE MESHES (we have enough info now) */ diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp index 0c47d5777c7..ed31aadc011 100644 --- a/modules/gltf/gltf_state.cpp +++ b/modules/gltf/gltf_state.cpp @@ -90,6 +90,8 @@ void GLTFState::_bind_methods() { ClassDB::bind_method(D_METHOD("set_skeletons", "skeletons"), &GLTFState::set_skeletons); ClassDB::bind_method(D_METHOD("get_create_animations"), &GLTFState::get_create_animations); ClassDB::bind_method(D_METHOD("set_create_animations", "create_animations"), &GLTFState::set_create_animations); + ClassDB::bind_method(D_METHOD("get_import_as_skeleton_bones"), &GLTFState::get_import_as_skeleton_bones); + ClassDB::bind_method(D_METHOD("set_import_as_skeleton_bones", "import_as_skeleton_bones"), &GLTFState::set_import_as_skeleton_bones); ClassDB::bind_method(D_METHOD("get_animations"), &GLTFState::get_animations); ClassDB::bind_method(D_METHOD("set_animations", "animations"), &GLTFState::set_animations); ClassDB::bind_method(D_METHOD("get_scene_node", "idx"), &GLTFState::get_scene_node); @@ -125,6 +127,7 @@ void GLTFState::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "unique_animation_names", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_unique_animation_names", "get_unique_animation_names"); // Set ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "skeletons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_skeletons", "get_skeletons"); // Vector> ADD_PROPERTY(PropertyInfo(Variant::BOOL, "create_animations"), "set_create_animations", "get_create_animations"); // bool + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "import_as_skeleton_bones"), "set_import_as_skeleton_bones", "get_import_as_skeleton_bones"); // bool ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "animations", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_animations", "get_animations"); // Vector> ADD_PROPERTY(PropertyInfo(Variant::INT, "handle_binary_image", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_handle_binary_image", "get_handle_binary_image"); // enum @@ -337,6 +340,14 @@ void GLTFState::set_create_animations(bool p_create_animations) { create_animations = p_create_animations; } +bool GLTFState::get_import_as_skeleton_bones() { + return import_as_skeleton_bones; +} + +void GLTFState::set_import_as_skeleton_bones(bool p_import_as_skeleton_bones) { + import_as_skeleton_bones = p_import_as_skeleton_bones; +} + TypedArray GLTFState::get_animations() { return GLTFTemplateConvert::to_array(animations); } diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h index c7171e0e68c..c9efffa3aed 100644 --- a/modules/gltf/gltf_state.h +++ b/modules/gltf/gltf_state.h @@ -64,6 +64,7 @@ protected: bool force_generate_tangents = false; bool create_animations = true; bool force_disable_compression = false; + bool import_as_skeleton_bones = false; int handle_binary_image = HANDLE_BINARY_EXTRACT_TEXTURES; @@ -213,6 +214,9 @@ public: bool get_create_animations(); void set_create_animations(bool p_create_animations); + bool get_import_as_skeleton_bones(); + void set_import_as_skeleton_bones(bool p_import_as_skeleton_bones); + TypedArray get_animations(); void set_animations(TypedArray p_animations); diff --git a/modules/gltf/skin_tool.cpp b/modules/gltf/skin_tool.cpp index a008e870e6a..2fb55a5f9eb 100644 --- a/modules/gltf/skin_tool.cpp +++ b/modules/gltf/skin_tool.cpp @@ -285,7 +285,18 @@ void SkinTool::_recurse_children( Error SkinTool::_determine_skeletons( Vector> &skins, Vector> &nodes, - Vector> &skeletons) { + Vector> &skeletons, + const Vector &p_single_skeleton_roots) { + if (!p_single_skeleton_roots.is_empty()) { + Ref skin; + skin.instantiate(); + skin->set_name("godot_single_skeleton_root"); + for (GLTFNodeIndex i = 0; i < p_single_skeleton_roots.size(); i++) { + skin->joints.push_back(p_single_skeleton_roots[i]); + } + skins.push_back(skin); + } + // Using a disjoint set, we are going to potentially combine all skins that are actually branches // of a main skeleton, or treat skins defining the same set of nodes as ONE skeleton. // This is another unclear issue caused by the current glTF specification. diff --git a/modules/gltf/skin_tool.h b/modules/gltf/skin_tool.h index 8f7ab011baa..1ba95853f3b 100644 --- a/modules/gltf/skin_tool.h +++ b/modules/gltf/skin_tool.h @@ -84,7 +84,8 @@ public: static Error _determine_skeletons( Vector> &r_skins, Vector> &r_nodes, - Vector> &r_skeletons); + Vector> &r_skeletons, + const Vector &p_single_skeleton_roots); static Error _create_skeletons( HashSet &r_unique_names, Vector> &r_skins,