2
0
Эх сурвалжийг харах

Merge pull request #88824 from V-Sekai/retarget_silhouette_template

Retargeting option to use a template for silhouette.
Rémi Verschelde 1 жил өмнө
parent
commit
9a8fb26d91

+ 183 - 5
editor/import/3d/resource_importer_scene.cpp

@@ -53,6 +53,7 @@
 #include "scene/resources/3d/sphere_shape_3d.h"
 #include "scene/resources/3d/sphere_shape_3d.h"
 #include "scene/resources/3d/world_boundary_shape_3d.h"
 #include "scene/resources/3d/world_boundary_shape_3d.h"
 #include "scene/resources/animation.h"
 #include "scene/resources/animation.h"
+#include "scene/resources/bone_map.h"
 #include "scene/resources/packed_scene.h"
 #include "scene/resources/packed_scene.h"
 #include "scene/resources/resource_format_text.h"
 #include "scene/resources/resource_format_text.h"
 #include "scene/resources/surface_tool.h"
 #include "scene/resources/surface_tool.h"
@@ -1222,6 +1223,74 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap<
 	}
 	}
 
 
 	if (Object::cast_to<Skeleton3D>(p_node)) {
 	if (Object::cast_to<Skeleton3D>(p_node)) {
+		Ref<Animation> rest_animation;
+		float rest_animation_timestamp = 0.0;
+		Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_node);
+		if (skeleton != nullptr && int(node_settings.get("rest_pose/load_pose", 0)) != 0) {
+			String selected_animation_name = node_settings.get("rest_pose/selected_animation", String());
+			if (int(node_settings["rest_pose/load_pose"]) == 1) {
+				TypedArray<Node> children = p_root->find_children("*", "AnimationPlayer", true, false);
+				for (int node_i = 0; node_i < children.size(); node_i++) {
+					AnimationPlayer *anim_player = cast_to<AnimationPlayer>(children[node_i]);
+					ERR_CONTINUE(anim_player == nullptr);
+					List<StringName> anim_list;
+					anim_player->get_animation_list(&anim_list);
+					if (anim_list.size() == 1) {
+						selected_animation_name = anim_list[0];
+					}
+					rest_animation = anim_player->get_animation(selected_animation_name);
+					if (rest_animation.is_valid()) {
+						break;
+					}
+				}
+			} else if (int(node_settings["rest_pose/load_pose"]) == 2) {
+				Object *external_object = node_settings.get("rest_pose/external_animation_library", Variant());
+				rest_animation = external_object;
+				if (rest_animation.is_null()) {
+					Ref<AnimationLibrary> library(external_object);
+					if (library.is_valid()) {
+						List<StringName> anim_list;
+						library->get_animation_list(&anim_list);
+						if (anim_list.size() == 1) {
+							selected_animation_name = String(anim_list[0]);
+						}
+						rest_animation = library->get_animation(selected_animation_name);
+					}
+				}
+			}
+			rest_animation_timestamp = double(node_settings.get("rest_pose/selected_timestamp", 0.0));
+			if (rest_animation.is_valid()) {
+				for (int track_i = 0; track_i < rest_animation->get_track_count(); track_i++) {
+					NodePath path = rest_animation->track_get_path(track_i);
+					StringName node_path = path.get_concatenated_names();
+					if (String(node_path).begins_with("%")) {
+						continue; // Unique node names are commonly used with retargeted animations, which we do not want to use.
+					}
+					StringName skeleton_bone = path.get_concatenated_subnames();
+					if (skeleton_bone == StringName()) {
+						continue;
+					}
+					int bone_idx = skeleton->find_bone(skeleton_bone);
+					if (bone_idx == -1) {
+						continue;
+					}
+					switch (rest_animation->track_get_type(track_i)) {
+						case Animation::TYPE_POSITION_3D: {
+							Vector3 bone_position = rest_animation->position_track_interpolate(track_i, rest_animation_timestamp);
+							skeleton->set_bone_rest(bone_idx, Transform3D(skeleton->get_bone_rest(bone_idx).basis, bone_position));
+						} break;
+						case Animation::TYPE_ROTATION_3D: {
+							Quaternion bone_rotation = rest_animation->rotation_track_interpolate(track_i, rest_animation_timestamp);
+							Transform3D current_rest = skeleton->get_bone_rest(bone_idx);
+							skeleton->set_bone_rest(bone_idx, Transform3D(Basis(bone_rotation).scaled(current_rest.basis.get_scale()), current_rest.origin));
+						} break;
+						default:
+							break;
+					}
+				}
+			}
+		}
+
 		ObjectID node_id = p_node->get_instance_id();
 		ObjectID node_id = p_node->get_instance_id();
 		for (int i = 0; i < post_importer_plugins.size(); i++) {
 		for (int i = 0; i < post_importer_plugins.size(); i++) {
 			post_importer_plugins.write[i]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE, p_root, p_node, Ref<Resource>(), node_settings);
 			post_importer_plugins.write[i]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE, p_root, p_node, Ref<Resource>(), node_settings);
@@ -1810,6 +1879,34 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p
 		} break;
 		} break;
 		case INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE: {
 		case INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE: {
 			r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/skip_import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
 			r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "import/skip_import", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), false));
+			r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "rest_pose/load_pose", PROPERTY_HINT_ENUM, "Default Pose,Use AnimationPlayer,Load External Animation", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0));
+			r_options->push_back(ImportOption(PropertyInfo(Variant::OBJECT, "rest_pose/external_animation_library", PROPERTY_HINT_RESOURCE_TYPE, "Animation,AnimationLibrary", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), Variant()));
+			r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "rest_pose/selected_animation", PROPERTY_HINT_ENUM, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), ""));
+			r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "rest_pose/selected_timestamp", PROPERTY_HINT_RANGE, "0,1,0.001,or_greater,suffix:s", PROPERTY_USAGE_DEFAULT), 0.0f));
+			String mismatched_or_empty_profile_warning = String(
+					"The external rest animation is missing some bones. "
+					"Consider disabling Remove Immutable Tracks on the other file."); // TODO: translate.
+			r_options->push_back(ImportOption(
+					PropertyInfo(
+							Variant::STRING, U"rest_pose/\u26A0_validation_warning/mismatched_or_empty_profile",
+							PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY),
+					Variant(mismatched_or_empty_profile_warning)));
+			String profile_must_not_be_retargeted_warning = String(
+					"This external rest animation appears to have been imported with a BoneMap. "
+					"Disable the bone map when exporting a rest animation from the reference model."); // TODO: translate.
+			r_options->push_back(ImportOption(
+					PropertyInfo(
+							Variant::STRING, U"rest_pose/\u26A0_validation_warning/profile_must_not_be_retargeted",
+							PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY),
+					Variant(profile_must_not_be_retargeted_warning)));
+			String no_animation_warning = String(
+					"Select an animation: Find a FBX or glTF in a compatible rest pose "
+					"and export a compatible animation from its import settings."); // TODO: translate.
+			r_options->push_back(ImportOption(
+					PropertyInfo(
+							Variant::STRING, U"rest_pose//no_animation_chosen",
+							PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY),
+					Variant(no_animation_warning)));
 			r_options->push_back(ImportOption(PropertyInfo(Variant::OBJECT, "retarget/bone_map", PROPERTY_HINT_RESOURCE_TYPE, "BoneMap", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), Variant()));
 			r_options->push_back(ImportOption(PropertyInfo(Variant::OBJECT, "retarget/bone_map", PROPERTY_HINT_RESOURCE_TYPE, "BoneMap", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), Variant()));
 		} break;
 		} break;
 		default: {
 		default: {
@@ -1924,9 +2021,90 @@ bool ResourceImporterScene::get_internal_option_visibility(InternalImportCategor
 			}
 			}
 		} break;
 		} break;
 		case INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE: {
 		case INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE: {
-			const bool use_retarget = p_options["retarget/bone_map"].get_validated_object() != nullptr;
-			if (p_option != "retarget/bone_map" && p_option.begins_with("retarget/")) {
-				return use_retarget;
+			const bool use_retarget = Object::cast_to<BoneMap>(p_options["retarget/bone_map"].get_validated_object()) != nullptr;
+			if (!use_retarget && p_option != "retarget/bone_map" && p_option.begins_with("retarget/")) {
+				return false;
+			}
+			int rest_warning = 0;
+			if (p_option.begins_with("rest_pose/")) {
+				if (!p_options.has("rest_pose/load_pose") || int(p_options["rest_pose/load_pose"]) == 0) {
+					if (p_option != "rest_pose/load_pose") {
+						return false;
+					}
+				} else if (int(p_options["rest_pose/load_pose"]) == 1) {
+					if (p_option == "rest_pose/external_animation_library") {
+						return false;
+					}
+				} else if (int(p_options["rest_pose/load_pose"]) == 2) {
+					Object *res = p_options["rest_pose/external_animation_library"];
+					Ref<Animation> anim(res);
+					if (anim.is_valid() && p_option == "rest_pose/selected_animation") {
+						return false;
+					}
+					Ref<AnimationLibrary> library(res);
+					String selected_animation_name = p_options["rest_pose/selected_animation"];
+					if (library.is_valid()) {
+						List<StringName> anim_list;
+						library->get_animation_list(&anim_list);
+						if (anim_list.size() == 1) {
+							selected_animation_name = String(anim_list[0]);
+						}
+						if (library->has_animation(selected_animation_name)) {
+							anim = library->get_animation(selected_animation_name);
+						}
+					}
+					int found_bone_count = 0;
+					Ref<BoneMap> bone_map;
+					Ref<SkeletonProfile> prof;
+					if (p_options.has("retarget/bone_map")) {
+						bone_map = p_options["retarget/bone_map"];
+					}
+					if (bone_map.is_valid()) {
+						prof = bone_map->get_profile();
+					}
+					if (anim.is_valid()) {
+						HashSet<StringName> target_bones;
+						if (bone_map.is_valid() && prof.is_valid()) {
+							for (int target_i = 0; target_i < prof->get_bone_size(); target_i++) {
+								StringName skeleton_bone_name = bone_map->get_skeleton_bone_name(prof->get_bone_name(target_i));
+								if (skeleton_bone_name) {
+									target_bones.insert(skeleton_bone_name);
+								}
+							}
+						}
+						for (int track_i = 0; track_i < anim->get_track_count(); track_i++) {
+							if (anim->track_get_type(track_i) != Animation::TYPE_POSITION_3D && anim->track_get_type(track_i) != Animation::TYPE_ROTATION_3D) {
+								continue;
+							}
+							NodePath path = anim->track_get_path(track_i);
+							StringName node_path = path.get_concatenated_names();
+							StringName skeleton_bone = path.get_concatenated_subnames();
+							if (skeleton_bone) {
+								if (String(node_path).begins_with("%")) {
+									rest_warning = 1;
+								}
+								if (target_bones.has(skeleton_bone)) {
+									target_bones.erase(skeleton_bone);
+								}
+								found_bone_count++;
+							}
+						}
+						if ((found_bone_count < 15 || !target_bones.is_empty()) && rest_warning != 1) {
+							rest_warning = 2; // heuristic: animation targeted too few bones.
+						}
+					} else {
+						rest_warning = 3;
+					}
+				}
+				if (p_option.begins_with("rest_pose/") && p_option.ends_with("profile_must_not_be_retargeted")) {
+					return rest_warning == 1;
+				}
+				if (p_option.begins_with("rest_pose/") && p_option.ends_with("mismatched_or_empty_profile")) {
+					return rest_warning == 2;
+				}
+				if (p_option.begins_with("rest_pose/") && p_option.ends_with("no_animation_chosen")) {
+					return rest_warning == 3;
+				}
 			}
 			}
 		} break;
 		} break;
 		default: {
 		default: {
@@ -2145,8 +2323,8 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
 						merge_angle = mesh_settings["lods/normal_merge_angle"];
 						merge_angle = mesh_settings["lods/normal_merge_angle"];
 					}
 					}
 
 
-					if (mesh_settings.has("save_to_file/enabled") && bool(mesh_settings["save_to_file/enabled"]) && mesh_settings.has("save_to_file/path")) {
-						save_to_file = mesh_settings["save_to_file/path"];
+					if (bool(mesh_settings.get("save_to_file/enabled", false))) {
+						save_to_file = mesh_settings.get("save_to_file/path", String());
 						if (!save_to_file.is_resource_file()) {
 						if (!save_to_file.is_resource_file()) {
 							save_to_file = "";
 							save_to_file = "";
 						}
 						}

+ 65 - 5
editor/import/3d/scene_import_settings.cpp

@@ -50,6 +50,8 @@ class SceneImportSettingsData : public Object {
 	HashMap<StringName, Variant> current;
 	HashMap<StringName, Variant> current;
 	HashMap<StringName, Variant> defaults;
 	HashMap<StringName, Variant> defaults;
 	List<ResourceImporter::ImportOption> options;
 	List<ResourceImporter::ImportOption> options;
+	Vector<String> animation_list;
+
 	bool hide_options = false;
 	bool hide_options = false;
 	String path;
 	String path;
 
 
@@ -96,6 +98,7 @@ class SceneImportSettingsData : public Object {
 		}
 		}
 		return false;
 		return false;
 	}
 	}
+
 	bool _get(const StringName &p_name, Variant &r_ret) const {
 	bool _get(const StringName &p_name, Variant &r_ret) const {
 		if (settings) {
 		if (settings) {
 			if (settings->has(p_name)) {
 			if (settings->has(p_name)) {
@@ -109,29 +112,81 @@ class SceneImportSettingsData : public Object {
 		}
 		}
 		return false;
 		return false;
 	}
 	}
-	void _get_property_list(List<PropertyInfo> *p_list) const {
+
+	void handle_special_properties(PropertyInfo &r_option) const {
+		ERR_FAIL_NULL(settings);
+		if (r_option.name == "rest_pose/load_pose") {
+			if (!settings->has("rest_pose/load_pose") || int((*settings)["rest_pose/load_pose"]) != 2) {
+				(*settings)["rest_pose/external_animation_library"] = Variant();
+			}
+		}
+		if (r_option.name == "rest_pose/selected_animation") {
+			if (!settings->has("rest_pose/load_pose")) {
+				return;
+			}
+			String hint_string;
+
+			switch (int((*settings)["rest_pose/load_pose"])) {
+				case 1: {
+					hint_string = String(",").join(animation_list);
+					if (animation_list.size() == 1) {
+						(*settings)["rest_pose/selected_animation"] = animation_list[0];
+					}
+				} break;
+				case 2: {
+					Object *res = (*settings)["rest_pose/external_animation_library"];
+					Ref<Animation> anim(res);
+					Ref<AnimationLibrary> library(res);
+					if (anim.is_valid()) {
+						hint_string = anim->get_name();
+					}
+					if (library.is_valid()) {
+						List<StringName> anim_names;
+						library->get_animation_list(&anim_names);
+						if (anim_names.size() == 1) {
+							(*settings)["rest_pose/selected_animation"] = String(anim_names[0]);
+						}
+						for (StringName anim_name : anim_names) {
+							hint_string += "," + anim_name; // Include preceding, as a catch-all.
+						}
+					}
+				} break;
+				default:
+					break;
+			}
+			r_option.hint = PROPERTY_HINT_ENUM;
+			r_option.hint_string = hint_string;
+		}
+	}
+
+	void _get_property_list(List<PropertyInfo> *r_list) const {
 		if (hide_options) {
 		if (hide_options) {
 			return;
 			return;
 		}
 		}
 		for (const ResourceImporter::ImportOption &E : options) {
 		for (const ResourceImporter::ImportOption &E : options) {
+			PropertyInfo option = E.option;
 			if (SceneImportSettingsDialog::get_singleton()->is_editing_animation()) {
 			if (SceneImportSettingsDialog::get_singleton()->is_editing_animation()) {
 				if (category == ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MAX) {
 				if (category == ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MAX) {
 					if (ResourceImporterScene::get_animation_singleton()->get_option_visibility(path, E.option.name, current)) {
 					if (ResourceImporterScene::get_animation_singleton()->get_option_visibility(path, E.option.name, current)) {
-						p_list->push_back(E.option);
+						handle_special_properties(option);
+						r_list->push_back(option);
 					}
 					}
 				} else {
 				} else {
 					if (ResourceImporterScene::get_animation_singleton()->get_internal_option_visibility(category, E.option.name, current)) {
 					if (ResourceImporterScene::get_animation_singleton()->get_internal_option_visibility(category, E.option.name, current)) {
-						p_list->push_back(E.option);
+						handle_special_properties(option);
+						r_list->push_back(option);
 					}
 					}
 				}
 				}
 			} else {
 			} else {
 				if (category == ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MAX) {
 				if (category == ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MAX) {
 					if (ResourceImporterScene::get_scene_singleton()->get_option_visibility(path, E.option.name, current)) {
 					if (ResourceImporterScene::get_scene_singleton()->get_option_visibility(path, E.option.name, current)) {
-						p_list->push_back(E.option);
+						handle_special_properties(option);
+						r_list->push_back(option);
 					}
 					}
 				} else {
 				} else {
 					if (ResourceImporterScene::get_scene_singleton()->get_internal_option_visibility(category, E.option.name, current)) {
 					if (ResourceImporterScene::get_scene_singleton()->get_internal_option_visibility(category, E.option.name, current)) {
-						p_list->push_back(E.option);
+						handle_special_properties(option);
+						r_list->push_back(option);
 					}
 					}
 				}
 				}
 			}
 			}
@@ -376,10 +431,15 @@ void SceneImportSettingsDialog::_fill_scene(Node *p_node, TreeItem *p_parent_ite
 
 
 	AnimationPlayer *anim_node = Object::cast_to<AnimationPlayer>(p_node);
 	AnimationPlayer *anim_node = Object::cast_to<AnimationPlayer>(p_node);
 	if (anim_node) {
 	if (anim_node) {
+		Vector<String> animation_list;
 		List<StringName> animations;
 		List<StringName> animations;
 		anim_node->get_animation_list(&animations);
 		anim_node->get_animation_list(&animations);
 		for (const StringName &E : animations) {
 		for (const StringName &E : animations) {
 			_fill_animation(scene_tree, anim_node->get_animation(E), E, item);
 			_fill_animation(scene_tree, anim_node->get_animation(E), E, item);
+			animation_list.append(E);
+		}
+		if (scene_import_settings_data != nullptr) {
+			scene_import_settings_data->animation_list = animation_list;
 		}
 		}
 	}
 	}