Browse Source

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.
Lyuma 1 year ago
parent
commit
652ef966f9

+ 3 - 0
doc/classes/ResourceImporterScene.xml

@@ -53,6 +53,9 @@
 		<member name="nodes/apply_root_scale" type="bool" setter="" getter="" default="true">
 			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.
 		</member>
+		<member name="nodes/import_as_skeleton_bones" type="bool" setter="" getter="" default="false">
+			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.
+		</member>
 		<member name="nodes/root_name" type="String" setter="" getter="" default="&quot;&quot;">
 			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.
 		</member>

+ 1 - 0
editor/import/3d/resource_importer_scene.cpp

@@ -1932,6 +1932,7 @@ void ResourceImporterScene::get_import_options(const String &p_path, List<Import
 
 	r_options->push_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));

+ 16 - 0
editor/import/3d/scene_import_settings.cpp

@@ -432,6 +432,16 @@ void SceneImportSettingsDialog::_update_view_gizmos() {
 	if (!is_visible()) {
 		return;
 	}
+	const HashMap<StringName, Variant> &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<String, NodeData> &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<StringName, Variant> &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);
 }
 

+ 1 - 0
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<StandardMaterial3D> collider_mat;
 

+ 3 - 0
modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp

@@ -104,6 +104,9 @@ Node *EditorSceneFormatImporterFBX2GLTF::import_scene(const String &p_path, uint
 	gltf.instantiate();
 	Ref<GLTFState> 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) {

+ 6 - 0
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) {

+ 1 - 1
modules/fbx/fbx_document.cpp

@@ -2086,7 +2086,7 @@ Error FBXDocument::_parse_fbx_state(Ref<FBXState> 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<GLTFNodeIndex>());
 	ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
 
 	/* CREATE SKELETONS */

+ 5 - 0
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.
 		</member>
 		<member name="glb_data" type="PackedByteArray" setter="set_glb_data" getter="get_glb_data" default="PackedByteArray()">
+			The binary buffer attached to a .glb file.
+		</member>
+		<member name="import_as_skeleton_bones" type="bool" setter="set_import_as_skeleton_bones" getter="get_import_as_skeleton_bones" default="false">
+			True to force all GLTFNodes in the document to be bones of a single Skeleton3D godot node.
 		</member>
 		<member name="json" type="Dictionary" setter="set_json" getter="get_json" default="{}">
+			The original raw JSON document corresponding to this GLTFState.
 		</member>
 		<member name="major_version" type="int" setter="set_major_version" getter="get_major_version" default="0">
 		</member>

+ 3 - 0
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) {

+ 1 - 1
modules/gltf/gltf_document.cpp

@@ -6924,7 +6924,7 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> 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<GLTFNodeIndex>());
 	ERR_FAIL_COND_V(err != OK, ERR_PARSE_ERROR);
 
 	/* PARSE MESHES (we have enough info now) */

+ 11 - 0
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<String>
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "skeletons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_skeletons", "get_skeletons"); // Vector<Ref<GLTFSkeleton>>
 	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<Ref<GLTFAnimation>>
 	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<GLTFAnimation> GLTFState::get_animations() {
 	return GLTFTemplateConvert::to_array(animations);
 }

+ 4 - 0
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<GLTFAnimation> get_animations();
 	void set_animations(TypedArray<GLTFAnimation> p_animations);
 

+ 12 - 1
modules/gltf/skin_tool.cpp

@@ -285,7 +285,18 @@ void SkinTool::_recurse_children(
 Error SkinTool::_determine_skeletons(
 		Vector<Ref<GLTFSkin>> &skins,
 		Vector<Ref<GLTFNode>> &nodes,
-		Vector<Ref<GLTFSkeleton>> &skeletons) {
+		Vector<Ref<GLTFSkeleton>> &skeletons,
+		const Vector<GLTFNodeIndex> &p_single_skeleton_roots) {
+	if (!p_single_skeleton_roots.is_empty()) {
+		Ref<GLTFSkin> 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.

+ 2 - 1
modules/gltf/skin_tool.h

@@ -84,7 +84,8 @@ public:
 	static Error _determine_skeletons(
 			Vector<Ref<GLTFSkin>> &r_skins,
 			Vector<Ref<GLTFNode>> &r_nodes,
-			Vector<Ref<GLTFSkeleton>> &r_skeletons);
+			Vector<Ref<GLTFSkeleton>> &r_skeletons,
+			const Vector<GLTFNodeIndex> &p_single_skeleton_roots);
 	static Error _create_skeletons(
 			HashSet<String> &r_unique_names,
 			Vector<Ref<GLTFSkin>> &r_skins,