Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Retargeting option to use a template for silhouette. #88824

Merged
merged 1 commit into from
Mar 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 183 additions & 5 deletions editor/import/3d/resource_importer_scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
#include "scene/resources/3d/sphere_shape_3d.h"
#include "scene/resources/3d/world_boundary_shape_3d.h"
#include "scene/resources/animation.h"
#include "scene/resources/bone_map.h"
#include "scene/resources/packed_scene.h"
#include "scene/resources/resource_format_text.h"
#include "scene/resources/surface_tool.h"
Expand Down Expand Up @@ -1157,6 +1158,74 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap<
}

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();
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);
Expand Down Expand Up @@ -1745,6 +1814,34 @@ void ResourceImporterScene::get_internal_import_options(InternalImportCategory p
} break;
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::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()));
} break;
default: {
Expand Down Expand Up @@ -1859,9 +1956,90 @@ bool ResourceImporterScene::get_internal_option_visibility(InternalImportCategor
}
} break;
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;
default: {
Expand Down Expand Up @@ -2079,8 +2257,8 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
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()) {
save_to_file = "";
}
Expand Down
70 changes: 65 additions & 5 deletions editor/import/3d/scene_import_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class SceneImportSettingsData : public Object {
HashMap<StringName, Variant> current;
HashMap<StringName, Variant> defaults;
List<ResourceImporter::ImportOption> options;
Vector<String> animation_list;

bool hide_options = false;
String path;

Expand Down Expand Up @@ -96,6 +98,7 @@ class SceneImportSettingsData : public Object {
}
return false;
}

bool _get(const StringName &p_name, Variant &r_ret) const {
if (settings) {
if (settings->has(p_name)) {
Expand All @@ -109,29 +112,81 @@ class SceneImportSettingsData : public Object {
}
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) {
return;
}
for (const ResourceImporter::ImportOption &E : options) {
PropertyInfo option = E.option;
if (SceneImportSettingsDialog::get_singleton()->is_editing_animation()) {
if (category == ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MAX) {
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 {
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 {
if (category == ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_MAX) {
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 {
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);
}
}
}
Expand Down Expand Up @@ -376,10 +431,15 @@ void SceneImportSettingsDialog::_fill_scene(Node *p_node, TreeItem *p_parent_ite

AnimationPlayer *anim_node = Object::cast_to<AnimationPlayer>(p_node);
if (anim_node) {
Vector<String> animation_list;
List<StringName> animations;
anim_node->get_animation_list(&animations);
for (const StringName &E : animations) {
_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;
}
}

Expand Down
Loading