500 lines
17 KiB
C++
500 lines
17 KiB
C++
/*************************************************************************/
|
|
/* bone_map_editor_plugin.cpp */
|
|
/*************************************************************************/
|
|
/* This file is part of: */
|
|
/* GODOT ENGINE */
|
|
/* https://godotengine.org */
|
|
/*************************************************************************/
|
|
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
|
|
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
|
|
/* */
|
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
/* a copy of this software and associated documentation files (the */
|
|
/* "Software"), to deal in the Software without restriction, including */
|
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
/* the following conditions: */
|
|
/* */
|
|
/* The above copyright notice and this permission notice shall be */
|
|
/* included in all copies or substantial portions of the Software. */
|
|
/* */
|
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
/*************************************************************************/
|
|
|
|
#include "bone_map_editor_plugin.h"
|
|
|
|
#include "editor/editor_scale.h"
|
|
#include "editor/import/post_import_plugin_skeleton_renamer.h"
|
|
#include "editor/import/post_import_plugin_skeleton_rest_fixer.h"
|
|
#include "editor/import/post_import_plugin_skeleton_track_organizer.h"
|
|
#include "editor/import/scene_import_settings.h"
|
|
|
|
void BoneMapperButton::fetch_textures() {
|
|
if (selected) {
|
|
set_normal_texture(get_theme_icon(SNAME("BoneMapperHandleSelected"), SNAME("EditorIcons")));
|
|
} else {
|
|
set_normal_texture(get_theme_icon(SNAME("BoneMapperHandle"), SNAME("EditorIcons")));
|
|
}
|
|
set_offset(SIDE_LEFT, 0);
|
|
set_offset(SIDE_RIGHT, 0);
|
|
set_offset(SIDE_TOP, 0);
|
|
set_offset(SIDE_BOTTOM, 0);
|
|
|
|
circle = memnew(TextureRect);
|
|
circle->set_texture(get_theme_icon(SNAME("BoneMapperHandleCircle"), SNAME("EditorIcons")));
|
|
add_child(circle);
|
|
set_state(BONE_MAP_STATE_UNSET);
|
|
}
|
|
|
|
StringName BoneMapperButton::get_profile_bone_name() const {
|
|
return profile_bone_name;
|
|
}
|
|
|
|
void BoneMapperButton::set_state(BoneMapState p_state) {
|
|
switch (p_state) {
|
|
case BONE_MAP_STATE_UNSET: {
|
|
circle->set_modulate(EditorSettings::get_singleton()->get("editors/bone_mapper/handle_colors/unset"));
|
|
} break;
|
|
case BONE_MAP_STATE_SET: {
|
|
circle->set_modulate(EditorSettings::get_singleton()->get("editors/bone_mapper/handle_colors/set"));
|
|
} break;
|
|
case BONE_MAP_STATE_MISSING: {
|
|
circle->set_modulate(EditorSettings::get_singleton()->get("editors/bone_mapper/handle_colors/missing"));
|
|
} break;
|
|
case BONE_MAP_STATE_ERROR: {
|
|
circle->set_modulate(EditorSettings::get_singleton()->get("editors/bone_mapper/handle_colors/error"));
|
|
} break;
|
|
default: {
|
|
} break;
|
|
}
|
|
}
|
|
|
|
bool BoneMapperButton::is_require() const {
|
|
return require;
|
|
}
|
|
|
|
void BoneMapperButton::_notification(int p_what) {
|
|
switch (p_what) {
|
|
case NOTIFICATION_ENTER_TREE: {
|
|
fetch_textures();
|
|
} break;
|
|
}
|
|
}
|
|
|
|
BoneMapperButton::BoneMapperButton(const StringName p_profile_bone_name, bool p_require, bool p_selected) {
|
|
profile_bone_name = p_profile_bone_name;
|
|
require = p_require;
|
|
selected = p_selected;
|
|
}
|
|
|
|
BoneMapperButton::~BoneMapperButton() {
|
|
}
|
|
|
|
void BoneMapperItem::create_editor() {
|
|
skeleton_bone_selector = memnew(EditorPropertyTextEnum);
|
|
skeleton_bone_selector->setup(skeleton_bone_names, false, true);
|
|
skeleton_bone_selector->set_label(profile_bone_name);
|
|
skeleton_bone_selector->set_selectable(false);
|
|
skeleton_bone_selector->set_object_and_property(bone_map.ptr(), "bone_map/" + String(profile_bone_name));
|
|
skeleton_bone_selector->update_property();
|
|
skeleton_bone_selector->connect("property_changed", callable_mp(this, &BoneMapperItem::_value_changed));
|
|
add_child(skeleton_bone_selector);
|
|
}
|
|
|
|
void BoneMapperItem::_update_property() {
|
|
if (skeleton_bone_selector->get_edited_object() && skeleton_bone_selector->get_edited_property()) {
|
|
skeleton_bone_selector->update_property();
|
|
}
|
|
}
|
|
|
|
void BoneMapperItem::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
|
|
bone_map->set(p_property, p_value);
|
|
}
|
|
|
|
void BoneMapperItem::_notification(int p_what) {
|
|
switch (p_what) {
|
|
case NOTIFICATION_ENTER_TREE: {
|
|
create_editor();
|
|
bone_map->connect("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property));
|
|
} break;
|
|
case NOTIFICATION_EXIT_TREE: {
|
|
if (!bone_map.is_null() && bone_map->is_connected("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property))) {
|
|
bone_map->disconnect("bone_map_updated", callable_mp(this, &BoneMapperItem::_update_property));
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
|
|
void BoneMapperItem::_bind_methods() {
|
|
}
|
|
|
|
BoneMapperItem::BoneMapperItem(Ref<BoneMap> &p_bone_map, PackedStringArray p_skeleton_bone_names, const StringName &p_profile_bone_name) {
|
|
bone_map = p_bone_map;
|
|
skeleton_bone_names = p_skeleton_bone_names;
|
|
profile_bone_name = p_profile_bone_name;
|
|
}
|
|
|
|
BoneMapperItem::~BoneMapperItem() {
|
|
}
|
|
|
|
void BoneMapper::create_editor() {
|
|
profile_group_selector = memnew(EditorPropertyEnum);
|
|
profile_group_selector->set_label("Group");
|
|
profile_group_selector->set_selectable(false);
|
|
profile_group_selector->set_object_and_property(this, "current_group_idx");
|
|
profile_group_selector->update_property();
|
|
profile_group_selector->connect("property_changed", callable_mp(this, &BoneMapper::_value_changed));
|
|
add_child(profile_group_selector);
|
|
|
|
bone_mapper_field = memnew(AspectRatioContainer);
|
|
bone_mapper_field->set_stretch_mode(AspectRatioContainer::STRETCH_FIT);
|
|
bone_mapper_field->set_custom_minimum_size(Vector2(0, 256.0) * EDSCALE);
|
|
bone_mapper_field->set_h_size_flags(Control::SIZE_FILL);
|
|
add_child(bone_mapper_field);
|
|
|
|
profile_bg = memnew(ColorRect);
|
|
profile_bg->set_color(Color(0, 0, 0, 1));
|
|
profile_bg->set_h_size_flags(Control::SIZE_FILL);
|
|
profile_bg->set_v_size_flags(Control::SIZE_FILL);
|
|
bone_mapper_field->add_child(profile_bg);
|
|
|
|
profile_texture = memnew(TextureRect);
|
|
profile_texture->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
|
|
profile_texture->set_ignore_texture_size(true);
|
|
profile_texture->set_h_size_flags(Control::SIZE_FILL);
|
|
profile_texture->set_v_size_flags(Control::SIZE_FILL);
|
|
bone_mapper_field->add_child(profile_texture);
|
|
|
|
mapper_item_vbox = memnew(VBoxContainer);
|
|
add_child(mapper_item_vbox);
|
|
|
|
separator = memnew(HSeparator);
|
|
add_child(separator);
|
|
|
|
recreate_items();
|
|
}
|
|
|
|
void BoneMapper::update_group_idx() {
|
|
if (!bone_map->get_profile().is_valid()) {
|
|
return;
|
|
}
|
|
|
|
PackedStringArray group_names;
|
|
int len = bone_map->get_profile()->get_group_size();
|
|
for (int i = 0; i < len; i++) {
|
|
group_names.push_back(bone_map->get_profile()->get_group_name(i));
|
|
}
|
|
if (current_group_idx >= len) {
|
|
current_group_idx = 0;
|
|
}
|
|
if (len > 0) {
|
|
profile_group_selector->setup(group_names);
|
|
profile_group_selector->update_property();
|
|
profile_group_selector->set_read_only(false);
|
|
}
|
|
}
|
|
|
|
void BoneMapper::set_current_group_idx(int p_group_idx) {
|
|
current_group_idx = p_group_idx;
|
|
recreate_editor();
|
|
}
|
|
|
|
int BoneMapper::get_current_group_idx() const {
|
|
return current_group_idx;
|
|
}
|
|
|
|
void BoneMapper::set_current_bone_idx(int p_bone_idx) {
|
|
current_bone_idx = p_bone_idx;
|
|
recreate_editor();
|
|
}
|
|
|
|
int BoneMapper::get_current_bone_idx() const {
|
|
return current_bone_idx;
|
|
}
|
|
|
|
void BoneMapper::recreate_editor() {
|
|
// Clear buttons.
|
|
int len = bone_mapper_buttons.size();
|
|
for (int i = 0; i < len; i++) {
|
|
profile_texture->remove_child(bone_mapper_buttons[i]);
|
|
memdelete(bone_mapper_buttons[i]);
|
|
}
|
|
bone_mapper_buttons.clear();
|
|
|
|
// Organize mapper items.
|
|
len = bone_mapper_items.size();
|
|
for (int i = 0; i < len; i++) {
|
|
bone_mapper_items[i]->set_visible(current_bone_idx == i);
|
|
}
|
|
|
|
Ref<SkeletonProfile> profile = bone_map->get_profile();
|
|
if (profile.is_valid()) {
|
|
SkeletonProfileHumanoid *hmn = Object::cast_to<SkeletonProfileHumanoid>(profile.ptr());
|
|
if (hmn) {
|
|
StringName hmn_group_name = profile->get_group_name(current_group_idx);
|
|
if (hmn_group_name == "Body") {
|
|
profile_texture->set_texture(get_theme_icon(SNAME("BoneMapHumanBody"), SNAME("EditorIcons")));
|
|
} else if (hmn_group_name == "Face") {
|
|
profile_texture->set_texture(get_theme_icon(SNAME("BoneMapHumanFace"), SNAME("EditorIcons")));
|
|
} else if (hmn_group_name == "LeftHand") {
|
|
profile_texture->set_texture(get_theme_icon(SNAME("BoneMapHumanLeftHand"), SNAME("EditorIcons")));
|
|
} else if (hmn_group_name == "RightHand") {
|
|
profile_texture->set_texture(get_theme_icon(SNAME("BoneMapHumanRightHand"), SNAME("EditorIcons")));
|
|
}
|
|
} else {
|
|
profile_texture->set_texture(profile->get_texture(current_group_idx));
|
|
}
|
|
} else {
|
|
profile_texture->set_texture(Ref<Texture2D>());
|
|
}
|
|
|
|
if (!profile.is_valid()) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
if (profile->get_group(i) == profile->get_group_name(current_group_idx)) {
|
|
BoneMapperButton *mb = memnew(BoneMapperButton(profile->get_bone_name(i), profile->is_require(i), current_bone_idx == i));
|
|
mb->connect("pressed", callable_mp(this, &BoneMapper::set_current_bone_idx), varray(i), CONNECT_DEFERRED);
|
|
mb->set_h_grow_direction(GROW_DIRECTION_BOTH);
|
|
mb->set_v_grow_direction(GROW_DIRECTION_BOTH);
|
|
Vector2 vc = profile->get_handle_offset(i);
|
|
bone_mapper_buttons.push_back(mb);
|
|
profile_texture->add_child(mb);
|
|
mb->set_anchor(SIDE_LEFT, vc.x);
|
|
mb->set_anchor(SIDE_RIGHT, vc.x);
|
|
mb->set_anchor(SIDE_TOP, vc.y);
|
|
mb->set_anchor(SIDE_BOTTOM, vc.y);
|
|
}
|
|
}
|
|
|
|
_update_state();
|
|
}
|
|
|
|
void BoneMapper::clear_items() {
|
|
// Clear items.
|
|
int len = bone_mapper_items.size();
|
|
for (int i = 0; i < len; i++) {
|
|
mapper_item_vbox->remove_child(bone_mapper_items[i]);
|
|
memdelete(bone_mapper_items[i]);
|
|
}
|
|
bone_mapper_items.clear();
|
|
}
|
|
|
|
void BoneMapper::recreate_items() {
|
|
clear_items();
|
|
// Create items by profile.
|
|
Ref<SkeletonProfile> profile = bone_map->get_profile();
|
|
if (profile.is_valid()) {
|
|
PackedStringArray skeleton_bone_names;
|
|
int len = skeleton->get_bone_count();
|
|
for (int i = 0; i < len; i++) {
|
|
skeleton_bone_names.push_back(skeleton->get_bone_name(i));
|
|
}
|
|
|
|
len = profile->get_bone_size();
|
|
for (int i = 0; i < len; i++) {
|
|
StringName bn = profile->get_bone_name(i);
|
|
bone_mapper_items.append(memnew(BoneMapperItem(bone_map, skeleton_bone_names, bn)));
|
|
mapper_item_vbox->add_child(bone_mapper_items[i]);
|
|
}
|
|
}
|
|
|
|
update_group_idx();
|
|
recreate_editor();
|
|
}
|
|
|
|
void BoneMapper::_update_state() {
|
|
int len = bone_mapper_buttons.size();
|
|
for (int i = 0; i < len; i++) {
|
|
StringName pbn = bone_mapper_buttons[i]->get_profile_bone_name();
|
|
StringName sbn = bone_map->get_skeleton_bone_name(pbn);
|
|
int bone_idx = skeleton->find_bone(sbn);
|
|
if (bone_idx >= 0) {
|
|
if (bone_map->get_skeleton_bone_name_count(sbn) == 1) {
|
|
Ref<SkeletonProfile> prof = bone_map->get_profile();
|
|
|
|
StringName parent_name = prof->get_bone_parent(prof->find_bone(pbn));
|
|
Vector<int> prof_parent_bones;
|
|
while (parent_name != StringName()) {
|
|
prof_parent_bones.push_back(skeleton->find_bone(bone_map->get_skeleton_bone_name(parent_name)));
|
|
if (prof->find_bone(parent_name) == -1) {
|
|
break;
|
|
}
|
|
parent_name = prof->get_bone_parent(prof->find_bone(parent_name));
|
|
}
|
|
|
|
int parent_id = skeleton->get_bone_parent(bone_idx);
|
|
Vector<int> skel_parent_bones;
|
|
while (parent_id >= 0) {
|
|
skel_parent_bones.push_back(parent_id);
|
|
parent_id = skeleton->get_bone_parent(parent_id);
|
|
}
|
|
|
|
bool is_broken = false;
|
|
for (int j = 0; j < prof_parent_bones.size(); j++) {
|
|
if (prof_parent_bones[j] != -1 && !skel_parent_bones.has(prof_parent_bones[j])) {
|
|
is_broken = true;
|
|
}
|
|
}
|
|
|
|
if (is_broken) {
|
|
bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_ERROR);
|
|
} else {
|
|
bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_SET);
|
|
}
|
|
} else {
|
|
bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_ERROR);
|
|
}
|
|
} else {
|
|
if (bone_mapper_buttons[i]->is_require()) {
|
|
bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_MISSING);
|
|
} else {
|
|
bone_mapper_buttons[i]->set_state(BoneMapperButton::BONE_MAP_STATE_UNSET);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BoneMapper::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
|
|
set(p_property, p_value);
|
|
recreate_editor();
|
|
}
|
|
|
|
void BoneMapper::_bind_methods() {
|
|
ClassDB::bind_method(D_METHOD("set_current_group_idx", "current_group_idx"), &BoneMapper::set_current_group_idx);
|
|
ClassDB::bind_method(D_METHOD("get_current_group_idx"), &BoneMapper::get_current_group_idx);
|
|
ClassDB::bind_method(D_METHOD("set_current_bone_idx", "current_bone_idx"), &BoneMapper::set_current_bone_idx);
|
|
ClassDB::bind_method(D_METHOD("get_current_bone_idx"), &BoneMapper::get_current_bone_idx);
|
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "current_group_idx"), "set_current_group_idx", "get_current_group_idx");
|
|
ADD_PROPERTY(PropertyInfo(Variant::INT, "current_bone_idx"), "set_current_bone_idx", "get_current_bone_idx");
|
|
}
|
|
|
|
void BoneMapper::_notification(int p_what) {
|
|
switch (p_what) {
|
|
case NOTIFICATION_ENTER_TREE: {
|
|
create_editor();
|
|
bone_map->connect("bone_map_updated", callable_mp(this, &BoneMapper::_update_state));
|
|
bone_map->connect("profile_updated", callable_mp(this, &BoneMapper::recreate_items));
|
|
} break;
|
|
case NOTIFICATION_EXIT_TREE: {
|
|
clear_items();
|
|
if (!bone_map.is_null()) {
|
|
if (bone_map->is_connected("bone_map_updated", callable_mp(this, &BoneMapper::_update_state))) {
|
|
bone_map->disconnect("bone_map_updated", callable_mp(this, &BoneMapper::_update_state));
|
|
}
|
|
if (bone_map->is_connected("profile_updated", callable_mp(this, &BoneMapper::recreate_items))) {
|
|
bone_map->disconnect("profile_updated", callable_mp(this, &BoneMapper::recreate_items));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BoneMapper::BoneMapper(Skeleton3D *p_skeleton, Ref<BoneMap> &p_bone_map) {
|
|
skeleton = p_skeleton;
|
|
bone_map = p_bone_map;
|
|
}
|
|
|
|
BoneMapper::~BoneMapper() {
|
|
}
|
|
|
|
void BoneMapEditor::create_editors() {
|
|
if (!skeleton) {
|
|
return;
|
|
}
|
|
bone_mapper = memnew(BoneMapper(skeleton, bone_map));
|
|
add_child(bone_mapper);
|
|
}
|
|
|
|
void BoneMapEditor::fetch_objects() {
|
|
skeleton = nullptr;
|
|
// Hackey... but it may be the easist way to get a selected object from "ImporterScene".
|
|
SceneImportSettings *si = SceneImportSettings::get_singleton();
|
|
if (!si) {
|
|
return;
|
|
}
|
|
if (!si->is_visible()) {
|
|
return;
|
|
}
|
|
Node *selected = si->get_selected_node();
|
|
if (selected) {
|
|
Skeleton3D *sk = Object::cast_to<Skeleton3D>(selected);
|
|
if (!sk) {
|
|
return;
|
|
}
|
|
skeleton = sk;
|
|
} else {
|
|
// Editor should not exist.
|
|
skeleton = nullptr;
|
|
}
|
|
}
|
|
|
|
void BoneMapEditor::_notification(int p_what) {
|
|
switch (p_what) {
|
|
case NOTIFICATION_ENTER_TREE: {
|
|
fetch_objects();
|
|
create_editors();
|
|
} break;
|
|
case NOTIFICATION_EXIT_TREE: {
|
|
if (bone_mapper) {
|
|
remove_child(bone_mapper);
|
|
bone_mapper->queue_delete();
|
|
}
|
|
skeleton = nullptr;
|
|
} break;
|
|
}
|
|
}
|
|
|
|
BoneMapEditor::BoneMapEditor(Ref<BoneMap> &p_bone_map) {
|
|
bone_map = p_bone_map;
|
|
}
|
|
|
|
BoneMapEditor::~BoneMapEditor() {
|
|
}
|
|
|
|
bool EditorInspectorPluginBoneMap::can_handle(Object *p_object) {
|
|
return Object::cast_to<BoneMap>(p_object) != nullptr;
|
|
}
|
|
|
|
void EditorInspectorPluginBoneMap::parse_begin(Object *p_object) {
|
|
BoneMap *bm = Object::cast_to<BoneMap>(p_object);
|
|
if (!bm) {
|
|
return;
|
|
}
|
|
Ref<BoneMap> r(bm);
|
|
editor = memnew(BoneMapEditor(r));
|
|
add_custom_control(editor);
|
|
}
|
|
|
|
BoneMapEditorPlugin::BoneMapEditorPlugin() {
|
|
// Register properties in editor settings.
|
|
EDITOR_DEF("editors/bone_mapper/handle_colors/unset", Color(0.3, 0.3, 0.3));
|
|
EDITOR_DEF("editors/bone_mapper/handle_colors/set", Color(0.1, 0.6, 0.25));
|
|
EDITOR_DEF("editors/bone_mapper/handle_colors/missing", Color(0.8, 0.2, 0.8));
|
|
EDITOR_DEF("editors/bone_mapper/handle_colors/error", Color(0.8, 0.2, 0.2));
|
|
|
|
Ref<EditorInspectorPluginBoneMap> inspector_plugin;
|
|
inspector_plugin.instantiate();
|
|
add_inspector_plugin(inspector_plugin);
|
|
|
|
Ref<PostImportPluginSkeletonTrackOrganizer> post_import_plugin_track_organizer;
|
|
post_import_plugin_track_organizer.instantiate();
|
|
add_scene_post_import_plugin(post_import_plugin_track_organizer);
|
|
|
|
Ref<PostImportPluginSkeletonRenamer> post_import_plugin_renamer;
|
|
post_import_plugin_renamer.instantiate();
|
|
add_scene_post_import_plugin(post_import_plugin_renamer);
|
|
|
|
Ref<PostImportPluginSkeletonRestFixer> post_import_plugin_rest_fixer;
|
|
post_import_plugin_rest_fixer.instantiate();
|
|
add_scene_post_import_plugin(post_import_plugin_rest_fixer);
|
|
}
|