| 1 | /**************************************************************************/ |
| 2 | /* skeleton_3d_editor_plugin.cpp */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #include "skeleton_3d_editor_plugin.h" |
| 32 | |
| 33 | #include "core/io/resource_saver.h" |
| 34 | #include "editor/editor_node.h" |
| 35 | #include "editor/editor_properties.h" |
| 36 | #include "editor/editor_properties_vector.h" |
| 37 | #include "editor/editor_scale.h" |
| 38 | #include "editor/editor_settings.h" |
| 39 | #include "editor/editor_string_names.h" |
| 40 | #include "editor/editor_undo_redo_manager.h" |
| 41 | #include "editor/plugins/animation_player_editor_plugin.h" |
| 42 | #include "node_3d_editor_plugin.h" |
| 43 | #include "scene/3d/collision_shape_3d.h" |
| 44 | #include "scene/3d/joint_3d.h" |
| 45 | #include "scene/3d/mesh_instance_3d.h" |
| 46 | #include "scene/3d/physics_body_3d.h" |
| 47 | #include "scene/gui/separator.h" |
| 48 | #include "scene/gui/texture_rect.h" |
| 49 | #include "scene/resources/capsule_shape_3d.h" |
| 50 | #include "scene/resources/skeleton_profile.h" |
| 51 | #include "scene/resources/sphere_shape_3d.h" |
| 52 | #include "scene/resources/surface_tool.h" |
| 53 | #include "scene/scene_string_names.h" |
| 54 | |
| 55 | void BoneTransformEditor::create_editors() { |
| 56 | section = memnew(EditorInspectorSection); |
| 57 | section->setup("trf_properties" , label, this, Color(0.0f, 0.0f, 0.0f), true); |
| 58 | section->unfold(); |
| 59 | add_child(section); |
| 60 | |
| 61 | enabled_checkbox = memnew(EditorPropertyCheck()); |
| 62 | enabled_checkbox->set_label("Pose Enabled" ); |
| 63 | enabled_checkbox->set_selectable(false); |
| 64 | enabled_checkbox->connect("property_changed" , callable_mp(this, &BoneTransformEditor::_value_changed)); |
| 65 | section->get_vbox()->add_child(enabled_checkbox); |
| 66 | |
| 67 | // Position property. |
| 68 | position_property = memnew(EditorPropertyVector3()); |
| 69 | position_property->setup(-10000, 10000, 0.001f, true); |
| 70 | position_property->set_label("Position" ); |
| 71 | position_property->set_selectable(false); |
| 72 | position_property->connect("property_changed" , callable_mp(this, &BoneTransformEditor::_value_changed)); |
| 73 | position_property->connect("property_keyed" , callable_mp(this, &BoneTransformEditor::_property_keyed)); |
| 74 | section->get_vbox()->add_child(position_property); |
| 75 | |
| 76 | // Rotation property. |
| 77 | rotation_property = memnew(EditorPropertyQuaternion()); |
| 78 | rotation_property->setup(-10000, 10000, 0.001f, true); |
| 79 | rotation_property->set_label("Rotation" ); |
| 80 | rotation_property->set_selectable(false); |
| 81 | rotation_property->connect("property_changed" , callable_mp(this, &BoneTransformEditor::_value_changed)); |
| 82 | rotation_property->connect("property_keyed" , callable_mp(this, &BoneTransformEditor::_property_keyed)); |
| 83 | section->get_vbox()->add_child(rotation_property); |
| 84 | |
| 85 | // Scale property. |
| 86 | scale_property = memnew(EditorPropertyVector3()); |
| 87 | scale_property->setup(-10000, 10000, 0.001f, true); |
| 88 | scale_property->set_label("Scale" ); |
| 89 | scale_property->set_selectable(false); |
| 90 | scale_property->connect("property_changed" , callable_mp(this, &BoneTransformEditor::_value_changed)); |
| 91 | scale_property->connect("property_keyed" , callable_mp(this, &BoneTransformEditor::_property_keyed)); |
| 92 | section->get_vbox()->add_child(scale_property); |
| 93 | |
| 94 | // Transform/Matrix section. |
| 95 | rest_section = memnew(EditorInspectorSection); |
| 96 | rest_section->setup("trf_properties_transform" , "Rest" , this, Color(0.0f, 0.0f, 0.0f), true); |
| 97 | section->get_vbox()->add_child(rest_section); |
| 98 | |
| 99 | // Transform/Matrix property. |
| 100 | rest_matrix = memnew(EditorPropertyTransform3D()); |
| 101 | rest_matrix->setup(-10000, 10000, 0.001f, true); |
| 102 | rest_matrix->set_label("Transform" ); |
| 103 | rest_matrix->set_selectable(false); |
| 104 | rest_section->get_vbox()->add_child(rest_matrix); |
| 105 | } |
| 106 | |
| 107 | void BoneTransformEditor::_notification(int p_what) { |
| 108 | switch (p_what) { |
| 109 | case NOTIFICATION_THEME_CHANGED: { |
| 110 | const Color section_color = get_theme_color(SNAME("prop_subsection" ), EditorStringName(Editor)); |
| 111 | section->set_bg_color(section_color); |
| 112 | rest_section->set_bg_color(section_color); |
| 113 | } break; |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | void BoneTransformEditor::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { |
| 118 | if (updating) { |
| 119 | return; |
| 120 | } |
| 121 | if (skeleton) { |
| 122 | EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); |
| 123 | undo_redo->create_action(TTR("Set Bone Transform" ), UndoRedo::MERGE_ENDS); |
| 124 | undo_redo->add_undo_property(skeleton, p_property, skeleton->get(p_property)); |
| 125 | undo_redo->add_do_property(skeleton, p_property, p_value); |
| 126 | undo_redo->commit_action(); |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | BoneTransformEditor::BoneTransformEditor(Skeleton3D *p_skeleton) : |
| 131 | skeleton(p_skeleton) { |
| 132 | create_editors(); |
| 133 | } |
| 134 | |
| 135 | void BoneTransformEditor::set_keyable(const bool p_keyable) { |
| 136 | position_property->set_keying(p_keyable); |
| 137 | rotation_property->set_keying(p_keyable); |
| 138 | scale_property->set_keying(p_keyable); |
| 139 | } |
| 140 | |
| 141 | void BoneTransformEditor::set_target(const String &p_prop) { |
| 142 | enabled_checkbox->set_object_and_property(skeleton, p_prop + "enabled" ); |
| 143 | enabled_checkbox->update_property(); |
| 144 | |
| 145 | position_property->set_object_and_property(skeleton, p_prop + "position" ); |
| 146 | position_property->update_property(); |
| 147 | |
| 148 | rotation_property->set_object_and_property(skeleton, p_prop + "rotation" ); |
| 149 | rotation_property->update_property(); |
| 150 | |
| 151 | scale_property->set_object_and_property(skeleton, p_prop + "scale" ); |
| 152 | scale_property->update_property(); |
| 153 | |
| 154 | rest_matrix->set_object_and_property(skeleton, p_prop + "rest" ); |
| 155 | rest_matrix->update_property(); |
| 156 | } |
| 157 | |
| 158 | void BoneTransformEditor::_property_keyed(const String &p_path, bool p_advance) { |
| 159 | AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); |
| 160 | if (!te || !te->has_keying()) { |
| 161 | return; |
| 162 | } |
| 163 | te->_clear_selection(); |
| 164 | PackedStringArray split = p_path.split("/" ); |
| 165 | if (split.size() == 3 && split[0] == "bones" ) { |
| 166 | int bone_idx = split[1].to_int(); |
| 167 | if (split[2] == "position" ) { |
| 168 | te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_POSITION_3D, (Vector3)skeleton->get(p_path) / skeleton->get_motion_scale()); |
| 169 | } |
| 170 | if (split[2] == "rotation" ) { |
| 171 | te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_ROTATION_3D, skeleton->get(p_path)); |
| 172 | } |
| 173 | if (split[2] == "scale" ) { |
| 174 | te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_SCALE_3D, skeleton->get(p_path)); |
| 175 | } |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | void BoneTransformEditor::_update_properties() { |
| 180 | if (!skeleton) { |
| 181 | return; |
| 182 | } |
| 183 | int selected = Skeleton3DEditor::get_singleton()->get_selected_bone(); |
| 184 | List<PropertyInfo> props; |
| 185 | skeleton->get_property_list(&props); |
| 186 | for (const PropertyInfo &E : props) { |
| 187 | PackedStringArray split = E.name.split("/" ); |
| 188 | if (split.size() == 3 && split[0] == "bones" ) { |
| 189 | if (split[1].to_int() == selected) { |
| 190 | if (split[2] == "enabled" ) { |
| 191 | enabled_checkbox->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); |
| 192 | enabled_checkbox->update_property(); |
| 193 | enabled_checkbox->queue_redraw(); |
| 194 | } |
| 195 | if (split[2] == "position" ) { |
| 196 | position_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); |
| 197 | position_property->update_property(); |
| 198 | position_property->queue_redraw(); |
| 199 | } |
| 200 | if (split[2] == "rotation" ) { |
| 201 | rotation_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); |
| 202 | rotation_property->update_property(); |
| 203 | rotation_property->queue_redraw(); |
| 204 | } |
| 205 | if (split[2] == "scale" ) { |
| 206 | scale_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); |
| 207 | scale_property->update_property(); |
| 208 | scale_property->queue_redraw(); |
| 209 | } |
| 210 | if (split[2] == "rest" ) { |
| 211 | rest_matrix->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); |
| 212 | rest_matrix->update_property(); |
| 213 | rest_matrix->queue_redraw(); |
| 214 | } |
| 215 | } |
| 216 | } |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | Skeleton3DEditor *Skeleton3DEditor::singleton = nullptr; |
| 221 | |
| 222 | void Skeleton3DEditor::set_keyable(const bool p_keyable) { |
| 223 | keyable = p_keyable; |
| 224 | if (p_keyable) { |
| 225 | animation_hb->show(); |
| 226 | } else { |
| 227 | animation_hb->hide(); |
| 228 | } |
| 229 | }; |
| 230 | |
| 231 | void Skeleton3DEditor::set_bone_options_enabled(const bool p_bone_options_enabled) { |
| 232 | skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_RESET_SELECTED_POSES, !p_bone_options_enabled); |
| 233 | skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_SELECTED_POSES_TO_RESTS, !p_bone_options_enabled); |
| 234 | }; |
| 235 | |
| 236 | void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) { |
| 237 | if (!skeleton) { |
| 238 | return; |
| 239 | } |
| 240 | |
| 241 | switch (p_skeleton_option) { |
| 242 | case SKELETON_OPTION_RESET_ALL_POSES: { |
| 243 | reset_pose(true); |
| 244 | break; |
| 245 | } |
| 246 | case SKELETON_OPTION_RESET_SELECTED_POSES: { |
| 247 | reset_pose(false); |
| 248 | break; |
| 249 | } |
| 250 | case SKELETON_OPTION_ALL_POSES_TO_RESTS: { |
| 251 | pose_to_rest(true); |
| 252 | break; |
| 253 | } |
| 254 | case SKELETON_OPTION_SELECTED_POSES_TO_RESTS: { |
| 255 | pose_to_rest(false); |
| 256 | break; |
| 257 | } |
| 258 | case SKELETON_OPTION_CREATE_PHYSICAL_SKELETON: { |
| 259 | create_physical_skeleton(); |
| 260 | break; |
| 261 | } |
| 262 | case SKELETON_OPTION_EXPORT_SKELETON_PROFILE: { |
| 263 | export_skeleton_profile(); |
| 264 | break; |
| 265 | } |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | void Skeleton3DEditor::reset_pose(const bool p_all_bones) { |
| 270 | if (!skeleton) { |
| 271 | return; |
| 272 | } |
| 273 | const int bone_count = skeleton->get_bone_count(); |
| 274 | if (!bone_count) { |
| 275 | return; |
| 276 | } |
| 277 | |
| 278 | EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); |
| 279 | ur->create_action(TTR("Set Bone Transform" ), UndoRedo::MERGE_ENDS); |
| 280 | if (p_all_bones) { |
| 281 | for (int i = 0; i < bone_count; i++) { |
| 282 | ur->add_undo_method(skeleton, "set_bone_pose_position" , i, skeleton->get_bone_pose_position(i)); |
| 283 | ur->add_undo_method(skeleton, "set_bone_pose_rotation" , i, skeleton->get_bone_pose_rotation(i)); |
| 284 | ur->add_undo_method(skeleton, "set_bone_pose_scale" , i, skeleton->get_bone_pose_scale(i)); |
| 285 | } |
| 286 | ur->add_do_method(skeleton, "reset_bone_poses" ); |
| 287 | } else { |
| 288 | // Todo: Do method with multiple bone selection. |
| 289 | if (selected_bone == -1) { |
| 290 | ur->commit_action(); |
| 291 | return; |
| 292 | } |
| 293 | ur->add_undo_method(skeleton, "set_bone_pose_position" , selected_bone, skeleton->get_bone_pose_position(selected_bone)); |
| 294 | ur->add_undo_method(skeleton, "set_bone_pose_rotation" , selected_bone, skeleton->get_bone_pose_rotation(selected_bone)); |
| 295 | ur->add_undo_method(skeleton, "set_bone_pose_scale" , selected_bone, skeleton->get_bone_pose_scale(selected_bone)); |
| 296 | ur->add_do_method(skeleton, "reset_bone_pose" , selected_bone); |
| 297 | } |
| 298 | ur->commit_action(); |
| 299 | } |
| 300 | |
| 301 | void Skeleton3DEditor::insert_keys(const bool p_all_bones) { |
| 302 | if (!skeleton) { |
| 303 | return; |
| 304 | } |
| 305 | |
| 306 | bool pos_enabled = key_loc_button->is_pressed(); |
| 307 | bool rot_enabled = key_rot_button->is_pressed(); |
| 308 | bool scl_enabled = key_scale_button->is_pressed(); |
| 309 | |
| 310 | int bone_len = skeleton->get_bone_count(); |
| 311 | Node *root = EditorNode::get_singleton()->get_tree()->get_root(); |
| 312 | String path = root->get_path_to(skeleton); |
| 313 | |
| 314 | AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); |
| 315 | te->make_insert_queue(); |
| 316 | for (int i = 0; i < bone_len; i++) { |
| 317 | const String name = skeleton->get_bone_name(i); |
| 318 | |
| 319 | if (name.is_empty()) { |
| 320 | continue; |
| 321 | } |
| 322 | |
| 323 | if (pos_enabled && (p_all_bones || te->has_track(skeleton, name, Animation::TYPE_POSITION_3D))) { |
| 324 | te->insert_transform_key(skeleton, name, Animation::TYPE_POSITION_3D, skeleton->get_bone_pose_position(i) / skeleton->get_motion_scale()); |
| 325 | } |
| 326 | if (rot_enabled && (p_all_bones || te->has_track(skeleton, name, Animation::TYPE_ROTATION_3D))) { |
| 327 | te->insert_transform_key(skeleton, name, Animation::TYPE_ROTATION_3D, skeleton->get_bone_pose_rotation(i)); |
| 328 | } |
| 329 | if (scl_enabled && (p_all_bones || te->has_track(skeleton, name, Animation::TYPE_SCALE_3D))) { |
| 330 | te->insert_transform_key(skeleton, name, Animation::TYPE_SCALE_3D, skeleton->get_bone_pose_scale(i)); |
| 331 | } |
| 332 | } |
| 333 | te->commit_insert_queue(); |
| 334 | } |
| 335 | |
| 336 | void Skeleton3DEditor::pose_to_rest(const bool p_all_bones) { |
| 337 | if (!skeleton) { |
| 338 | return; |
| 339 | } |
| 340 | const int bone_count = skeleton->get_bone_count(); |
| 341 | if (!bone_count) { |
| 342 | return; |
| 343 | } |
| 344 | |
| 345 | EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); |
| 346 | ur->create_action(TTR("Set Bone Rest" ), UndoRedo::MERGE_ENDS); |
| 347 | if (p_all_bones) { |
| 348 | for (int i = 0; i < bone_count; i++) { |
| 349 | ur->add_do_method(skeleton, "set_bone_rest" , i, skeleton->get_bone_pose(i)); |
| 350 | ur->add_undo_method(skeleton, "set_bone_rest" , i, skeleton->get_bone_rest(i)); |
| 351 | } |
| 352 | } else { |
| 353 | // Todo: Do method with multiple bone selection. |
| 354 | if (selected_bone == -1) { |
| 355 | ur->commit_action(); |
| 356 | return; |
| 357 | } |
| 358 | ur->add_do_method(skeleton, "set_bone_rest" , selected_bone, skeleton->get_bone_pose(selected_bone)); |
| 359 | ur->add_undo_method(skeleton, "set_bone_rest" , selected_bone, skeleton->get_bone_rest(selected_bone)); |
| 360 | } |
| 361 | ur->commit_action(); |
| 362 | } |
| 363 | |
| 364 | void Skeleton3DEditor::create_physical_skeleton() { |
| 365 | EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); |
| 366 | ERR_FAIL_COND(!get_tree()); |
| 367 | Node *owner = get_tree()->get_edited_scene_root(); |
| 368 | |
| 369 | const int bone_count = skeleton->get_bone_count(); |
| 370 | |
| 371 | if (!bone_count) { |
| 372 | EditorNode::get_singleton()->show_warning(vformat(TTR("Cannot create a physical skeleton for a Skeleton3D node with no bones." ))); |
| 373 | return; |
| 374 | } |
| 375 | |
| 376 | Vector<BoneInfo> bones_infos; |
| 377 | bones_infos.resize(bone_count); |
| 378 | |
| 379 | ur->create_action(TTR("Create physical bones" ), UndoRedo::MERGE_ALL); |
| 380 | for (int bone_id = 0; bone_count > bone_id; ++bone_id) { |
| 381 | const int parent = skeleton->get_bone_parent(bone_id); |
| 382 | |
| 383 | if (parent < 0) { |
| 384 | bones_infos.write[bone_id].relative_rest = skeleton->get_bone_rest(bone_id); |
| 385 | } else { |
| 386 | const int parent_parent = skeleton->get_bone_parent(parent); |
| 387 | |
| 388 | bones_infos.write[bone_id].relative_rest = bones_infos[parent].relative_rest * skeleton->get_bone_rest(bone_id); |
| 389 | |
| 390 | // Create physical bone on parent. |
| 391 | if (!bones_infos[parent].physical_bone) { |
| 392 | PhysicalBone3D *physical_bone = create_physical_bone(parent, bone_id, bones_infos); |
| 393 | if (physical_bone && physical_bone->get_child(0)) { |
| 394 | CollisionShape3D *collision_shape = Object::cast_to<CollisionShape3D>(physical_bone->get_child(0)); |
| 395 | if (collision_shape) { |
| 396 | bones_infos.write[parent].physical_bone = physical_bone; |
| 397 | |
| 398 | ur->add_do_method(skeleton, "add_child" , physical_bone); |
| 399 | ur->add_do_method(physical_bone, "set_owner" , owner); |
| 400 | ur->add_do_method(collision_shape, "set_owner" , owner); |
| 401 | ur->add_do_property(physical_bone, "bone_name" , skeleton->get_bone_name(parent)); |
| 402 | |
| 403 | // Create joint between parent of parent. |
| 404 | if (parent_parent != -1) { |
| 405 | ur->add_do_method(physical_bone, "set_joint_type" , PhysicalBone3D::JOINT_TYPE_PIN); |
| 406 | } |
| 407 | |
| 408 | ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, physical_bone); |
| 409 | ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, collision_shape); |
| 410 | |
| 411 | ur->add_do_reference(physical_bone); |
| 412 | ur->add_undo_method(skeleton, "remove_child" , physical_bone); |
| 413 | } |
| 414 | } |
| 415 | } |
| 416 | } |
| 417 | } |
| 418 | ur->commit_action(); |
| 419 | } |
| 420 | |
| 421 | PhysicalBone3D *Skeleton3DEditor::create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos) { |
| 422 | const Transform3D child_rest = skeleton->get_bone_rest(bone_child_id); |
| 423 | |
| 424 | const real_t half_height(child_rest.origin.length() * 0.5); |
| 425 | const real_t radius(half_height * 0.2); |
| 426 | |
| 427 | CapsuleShape3D *bone_shape_capsule = memnew(CapsuleShape3D); |
| 428 | bone_shape_capsule->set_height(half_height * 2); |
| 429 | bone_shape_capsule->set_radius(radius); |
| 430 | |
| 431 | CollisionShape3D *bone_shape = memnew(CollisionShape3D); |
| 432 | bone_shape->set_shape(bone_shape_capsule); |
| 433 | bone_shape->set_name("CollisionShape3D" ); |
| 434 | |
| 435 | Transform3D capsule_transform; |
| 436 | capsule_transform.basis.rows[0] = Vector3(1, 0, 0); |
| 437 | capsule_transform.basis.rows[1] = Vector3(0, 0, 1); |
| 438 | capsule_transform.basis.rows[2] = Vector3(0, -1, 0); |
| 439 | bone_shape->set_transform(capsule_transform); |
| 440 | |
| 441 | /// Get an up vector not collinear with child rest origin |
| 442 | Vector3 up = Vector3(0, 1, 0); |
| 443 | if (up.cross(child_rest.origin).is_zero_approx()) { |
| 444 | up = Vector3(0, 0, 1); |
| 445 | } |
| 446 | |
| 447 | Transform3D body_transform; |
| 448 | body_transform.basis = Basis::looking_at(child_rest.origin, up); |
| 449 | body_transform.origin = body_transform.basis.xform(Vector3(0, 0, -half_height)); |
| 450 | |
| 451 | Transform3D joint_transform; |
| 452 | joint_transform.origin = Vector3(0, 0, half_height); |
| 453 | |
| 454 | PhysicalBone3D *physical_bone = memnew(PhysicalBone3D); |
| 455 | physical_bone->add_child(bone_shape); |
| 456 | physical_bone->set_name("Physical Bone " + skeleton->get_bone_name(bone_id)); |
| 457 | physical_bone->set_body_offset(body_transform); |
| 458 | physical_bone->set_joint_offset(joint_transform); |
| 459 | return physical_bone; |
| 460 | } |
| 461 | |
| 462 | void Skeleton3DEditor::export_skeleton_profile() { |
| 463 | if (!skeleton->get_bone_count()) { |
| 464 | EditorNode::get_singleton()->show_warning(vformat(TTR("Cannot export a SkeletonProfile for a Skeleton3D node with no bones." ))); |
| 465 | return; |
| 466 | } |
| 467 | |
| 468 | file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); |
| 469 | file_dialog->set_title(TTR("Export Skeleton Profile As..." )); |
| 470 | |
| 471 | List<String> exts; |
| 472 | ResourceLoader::get_recognized_extensions_for_type("SkeletonProfile" , &exts); |
| 473 | file_dialog->clear_filters(); |
| 474 | for (const String &K : exts) { |
| 475 | file_dialog->add_filter("*." + K); |
| 476 | } |
| 477 | |
| 478 | file_dialog->popup_file_dialog(); |
| 479 | } |
| 480 | |
| 481 | void Skeleton3DEditor::_file_selected(const String &p_file) { |
| 482 | // Export SkeletonProfile. |
| 483 | Ref<SkeletonProfile> sp(memnew(SkeletonProfile)); |
| 484 | |
| 485 | // Build SkeletonProfile. |
| 486 | sp->set_group_size(1); |
| 487 | |
| 488 | Vector<Vector2> handle_positions; |
| 489 | Vector2 position_max; |
| 490 | Vector2 position_min; |
| 491 | |
| 492 | const int bone_count = skeleton->get_bone_count(); |
| 493 | sp->set_bone_size(bone_count); |
| 494 | for (int i = 0; i < bone_count; i++) { |
| 495 | sp->set_bone_name(i, skeleton->get_bone_name(i)); |
| 496 | int parent = skeleton->get_bone_parent(i); |
| 497 | if (parent >= 0) { |
| 498 | sp->set_bone_parent(i, skeleton->get_bone_name(parent)); |
| 499 | } |
| 500 | sp->set_reference_pose(i, skeleton->get_bone_rest(i)); |
| 501 | |
| 502 | Transform3D grest = skeleton->get_bone_global_rest(i); |
| 503 | handle_positions.append(Vector2(grest.origin.x, grest.origin.y)); |
| 504 | if (i == 0) { |
| 505 | position_max = Vector2(grest.origin.x, grest.origin.y); |
| 506 | position_min = Vector2(grest.origin.x, grest.origin.y); |
| 507 | } else { |
| 508 | position_max.x = MAX(grest.origin.x, position_max.x); |
| 509 | position_max.y = MAX(grest.origin.y, position_max.y); |
| 510 | position_min.x = MIN(grest.origin.x, position_min.x); |
| 511 | position_min.y = MIN(grest.origin.y, position_min.y); |
| 512 | } |
| 513 | } |
| 514 | |
| 515 | // Layout handles provisionaly. |
| 516 | Vector2 bound = Vector2(position_max.x - position_min.x, position_max.y - position_min.y); |
| 517 | Vector2 center = Vector2((position_max.x + position_min.x) * 0.5, (position_max.y + position_min.y) * 0.5); |
| 518 | float nrm = MAX(bound.x, bound.y); |
| 519 | if (nrm > 0) { |
| 520 | for (int i = 0; i < bone_count; i++) { |
| 521 | handle_positions.write[i] = (handle_positions[i] - center) / nrm * 0.9; |
| 522 | sp->set_handle_offset(i, Vector2(0.5 + handle_positions[i].x, 0.5 - handle_positions[i].y)); |
| 523 | } |
| 524 | } |
| 525 | |
| 526 | Error err = ResourceSaver::save(sp, p_file); |
| 527 | |
| 528 | if (err != OK) { |
| 529 | EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving file: %s" ), p_file)); |
| 530 | return; |
| 531 | } |
| 532 | } |
| 533 | |
| 534 | Variant Skeleton3DEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { |
| 535 | TreeItem *selected = joint_tree->get_selected(); |
| 536 | |
| 537 | if (!selected) { |
| 538 | return Variant(); |
| 539 | } |
| 540 | |
| 541 | Ref<Texture> icon = selected->get_icon(0); |
| 542 | |
| 543 | VBoxContainer *vb = memnew(VBoxContainer); |
| 544 | HBoxContainer *hb = memnew(HBoxContainer); |
| 545 | TextureRect *tf = memnew(TextureRect); |
| 546 | tf->set_texture(icon); |
| 547 | tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); |
| 548 | hb->add_child(tf); |
| 549 | Label *label = memnew(Label(selected->get_text(0))); |
| 550 | hb->add_child(label); |
| 551 | vb->add_child(hb); |
| 552 | hb->set_modulate(Color(1, 1, 1, 1)); |
| 553 | |
| 554 | set_drag_preview(vb); |
| 555 | Dictionary drag_data; |
| 556 | drag_data["type" ] = "nodes" ; |
| 557 | drag_data["node" ] = selected; |
| 558 | |
| 559 | return drag_data; |
| 560 | } |
| 561 | |
| 562 | bool Skeleton3DEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { |
| 563 | TreeItem *target = joint_tree->get_item_at_position(p_point); |
| 564 | if (!target) { |
| 565 | return false; |
| 566 | } |
| 567 | |
| 568 | const String path = target->get_metadata(0); |
| 569 | if (!path.begins_with("bones/" )) { |
| 570 | return false; |
| 571 | } |
| 572 | |
| 573 | TreeItem *selected = Object::cast_to<TreeItem>(Dictionary(p_data)["node" ]); |
| 574 | if (target == selected) { |
| 575 | return false; |
| 576 | } |
| 577 | |
| 578 | const String path2 = target->get_metadata(0); |
| 579 | if (!path2.begins_with("bones/" )) { |
| 580 | return false; |
| 581 | } |
| 582 | |
| 583 | return true; |
| 584 | } |
| 585 | |
| 586 | void Skeleton3DEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { |
| 587 | if (!can_drop_data_fw(p_point, p_data, p_from)) { |
| 588 | return; |
| 589 | } |
| 590 | |
| 591 | TreeItem *target = joint_tree->get_item_at_position(p_point); |
| 592 | TreeItem *selected = Object::cast_to<TreeItem>(Dictionary(p_data)["node" ]); |
| 593 | |
| 594 | const BoneId target_boneidx = String(target->get_metadata(0)).get_slicec('/', 1).to_int(); |
| 595 | const BoneId selected_boneidx = String(selected->get_metadata(0)).get_slicec('/', 1).to_int(); |
| 596 | |
| 597 | move_skeleton_bone(skeleton->get_path(), selected_boneidx, target_boneidx); |
| 598 | } |
| 599 | |
| 600 | void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_selected_boneidx, int32_t p_target_boneidx) { |
| 601 | Node *node = get_node_or_null(p_skeleton_path); |
| 602 | Skeleton3D *skeleton_node = Object::cast_to<Skeleton3D>(node); |
| 603 | ERR_FAIL_NULL(skeleton_node); |
| 604 | EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); |
| 605 | ur->create_action(TTR("Set Bone Parentage" )); |
| 606 | // If the target is a child of ourselves, we move only *us* and not our children. |
| 607 | if (skeleton_node->is_bone_parent_of(p_target_boneidx, p_selected_boneidx)) { |
| 608 | const BoneId parent_idx = skeleton_node->get_bone_parent(p_selected_boneidx); |
| 609 | const int bone_count = skeleton_node->get_bone_count(); |
| 610 | for (BoneId i = 0; i < bone_count; ++i) { |
| 611 | if (skeleton_node->get_bone_parent(i) == p_selected_boneidx) { |
| 612 | ur->add_undo_method(skeleton_node, "set_bone_parent" , i, skeleton_node->get_bone_parent(i)); |
| 613 | ur->add_do_method(skeleton_node, "set_bone_parent" , i, parent_idx); |
| 614 | skeleton_node->set_bone_parent(i, parent_idx); |
| 615 | } |
| 616 | } |
| 617 | } |
| 618 | ur->add_undo_method(skeleton_node, "set_bone_parent" , p_selected_boneidx, skeleton_node->get_bone_parent(p_selected_boneidx)); |
| 619 | ur->add_do_method(skeleton_node, "set_bone_parent" , p_selected_boneidx, p_target_boneidx); |
| 620 | skeleton_node->set_bone_parent(p_selected_boneidx, p_target_boneidx); |
| 621 | |
| 622 | update_joint_tree(); |
| 623 | ur->commit_action(); |
| 624 | } |
| 625 | |
| 626 | void Skeleton3DEditor::_joint_tree_selection_changed() { |
| 627 | TreeItem *selected = joint_tree->get_selected(); |
| 628 | if (selected) { |
| 629 | const String path = selected->get_metadata(0); |
| 630 | if (!path.begins_with("bones/" )) { |
| 631 | return; |
| 632 | } |
| 633 | const int b_idx = path.get_slicec('/', 1).to_int(); |
| 634 | selected_bone = b_idx; |
| 635 | if (pose_editor) { |
| 636 | const String bone_path = "bones/" + itos(b_idx) + "/" ; |
| 637 | pose_editor->set_target(bone_path); |
| 638 | pose_editor->set_keyable(keyable); |
| 639 | } |
| 640 | } |
| 641 | |
| 642 | if (pose_editor && pose_editor->is_inside_tree()) { |
| 643 | pose_editor->set_visible(selected); |
| 644 | } |
| 645 | set_bone_options_enabled(selected); |
| 646 | |
| 647 | _update_properties(); |
| 648 | _update_gizmo_visible(); |
| 649 | } |
| 650 | |
| 651 | // May be not used with single select mode. |
| 652 | void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button) { |
| 653 | } |
| 654 | |
| 655 | void Skeleton3DEditor::_update_properties() { |
| 656 | if (pose_editor) { |
| 657 | pose_editor->_update_properties(); |
| 658 | } |
| 659 | Node3DEditor::get_singleton()->update_transform_gizmo(); |
| 660 | } |
| 661 | |
| 662 | void Skeleton3DEditor::update_joint_tree() { |
| 663 | joint_tree->clear(); |
| 664 | |
| 665 | if (!skeleton) { |
| 666 | return; |
| 667 | } |
| 668 | |
| 669 | TreeItem *root = joint_tree->create_item(); |
| 670 | |
| 671 | HashMap<int, TreeItem *> items; |
| 672 | |
| 673 | items.insert(-1, root); |
| 674 | |
| 675 | Ref<Texture> bone_icon = get_editor_theme_icon(SNAME("BoneAttachment3D" )); |
| 676 | |
| 677 | Vector<int> bones_to_process = skeleton->get_parentless_bones(); |
| 678 | while (bones_to_process.size() > 0) { |
| 679 | int current_bone_idx = bones_to_process[0]; |
| 680 | bones_to_process.erase(current_bone_idx); |
| 681 | |
| 682 | const int parent_idx = skeleton->get_bone_parent(current_bone_idx); |
| 683 | TreeItem *parent_item = items.find(parent_idx)->value; |
| 684 | |
| 685 | TreeItem *joint_item = joint_tree->create_item(parent_item); |
| 686 | items.insert(current_bone_idx, joint_item); |
| 687 | |
| 688 | joint_item->set_text(0, skeleton->get_bone_name(current_bone_idx)); |
| 689 | joint_item->set_icon(0, bone_icon); |
| 690 | joint_item->set_selectable(0, true); |
| 691 | joint_item->set_metadata(0, "bones/" + itos(current_bone_idx)); |
| 692 | |
| 693 | // Add the bone's children to the list of bones to be processed. |
| 694 | Vector<int> current_bone_child_bones = skeleton->get_bone_children(current_bone_idx); |
| 695 | int child_bone_size = current_bone_child_bones.size(); |
| 696 | for (int i = 0; i < child_bone_size; i++) { |
| 697 | bones_to_process.push_back(current_bone_child_bones[i]); |
| 698 | } |
| 699 | } |
| 700 | } |
| 701 | |
| 702 | void Skeleton3DEditor::create_editors() { |
| 703 | set_h_size_flags(SIZE_EXPAND_FILL); |
| 704 | set_focus_mode(FOCUS_ALL); |
| 705 | |
| 706 | Node3DEditor *ne = Node3DEditor::get_singleton(); |
| 707 | AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); |
| 708 | |
| 709 | // Create File dialog. |
| 710 | file_dialog = memnew(EditorFileDialog); |
| 711 | file_dialog->connect("file_selected" , callable_mp(this, &Skeleton3DEditor::_file_selected)); |
| 712 | add_child(file_dialog); |
| 713 | |
| 714 | // Create Top Menu Bar. |
| 715 | HBoxContainer * = memnew(HBoxContainer); |
| 716 | ne->add_control_to_menu_panel(topmenu_bar); |
| 717 | |
| 718 | // Create Skeleton Option in Top Menu Bar. |
| 719 | skeleton_options = memnew(MenuButton); |
| 720 | topmenu_bar->add_child(skeleton_options); |
| 721 | |
| 722 | skeleton_options->set_text(TTR("Skeleton3D" )); |
| 723 | |
| 724 | // Skeleton options. |
| 725 | PopupMenu *p = skeleton_options->get_popup(); |
| 726 | p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/reset_all_poses" , TTR("Reset All Bone Poses" )), SKELETON_OPTION_RESET_ALL_POSES); |
| 727 | p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/reset_selected_poses" , TTR("Reset Selected Poses" )), SKELETON_OPTION_RESET_SELECTED_POSES); |
| 728 | p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/all_poses_to_rests" , TTR("Apply All Poses to Rests" )), SKELETON_OPTION_ALL_POSES_TO_RESTS); |
| 729 | p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/selected_poses_to_rests" , TTR("Apply Selected Poses to Rests" )), SKELETON_OPTION_SELECTED_POSES_TO_RESTS); |
| 730 | p->add_item(TTR("Create Physical Skeleton" ), SKELETON_OPTION_CREATE_PHYSICAL_SKELETON); |
| 731 | p->add_item(TTR("Export Skeleton Profile" ), SKELETON_OPTION_EXPORT_SKELETON_PROFILE); |
| 732 | |
| 733 | p->connect("id_pressed" , callable_mp(this, &Skeleton3DEditor::_on_click_skeleton_option)); |
| 734 | set_bone_options_enabled(false); |
| 735 | |
| 736 | Vector<Variant> button_binds; |
| 737 | button_binds.resize(1); |
| 738 | |
| 739 | edit_mode_button = memnew(Button); |
| 740 | topmenu_bar->add_child(edit_mode_button); |
| 741 | edit_mode_button->set_flat(true); |
| 742 | edit_mode_button->set_toggle_mode(true); |
| 743 | edit_mode_button->set_focus_mode(FOCUS_NONE); |
| 744 | edit_mode_button->set_tooltip_text(TTR("Edit Mode\nShow buttons on joints." )); |
| 745 | edit_mode_button->connect("toggled" , callable_mp(this, &Skeleton3DEditor::edit_mode_toggled)); |
| 746 | |
| 747 | edit_mode = false; |
| 748 | |
| 749 | if (skeleton) { |
| 750 | skeleton->add_child(handles_mesh_instance); |
| 751 | handles_mesh_instance->set_skeleton_path(NodePath("" )); |
| 752 | } |
| 753 | |
| 754 | // Keying buttons. |
| 755 | animation_hb = memnew(HBoxContainer); |
| 756 | topmenu_bar->add_child(animation_hb); |
| 757 | animation_hb->add_child(memnew(VSeparator)); |
| 758 | animation_hb->hide(); |
| 759 | |
| 760 | key_loc_button = memnew(Button); |
| 761 | key_loc_button->set_flat(true); |
| 762 | key_loc_button->set_toggle_mode(true); |
| 763 | key_loc_button->set_pressed(false); |
| 764 | key_loc_button->set_focus_mode(FOCUS_NONE); |
| 765 | key_loc_button->set_tooltip_text(TTR("Translation mask for inserting keys." )); |
| 766 | animation_hb->add_child(key_loc_button); |
| 767 | |
| 768 | key_rot_button = memnew(Button); |
| 769 | key_rot_button->set_flat(true); |
| 770 | key_rot_button->set_toggle_mode(true); |
| 771 | key_rot_button->set_pressed(true); |
| 772 | key_rot_button->set_focus_mode(FOCUS_NONE); |
| 773 | key_rot_button->set_tooltip_text(TTR("Rotation mask for inserting keys." )); |
| 774 | animation_hb->add_child(key_rot_button); |
| 775 | |
| 776 | key_scale_button = memnew(Button); |
| 777 | key_scale_button->set_flat(true); |
| 778 | key_scale_button->set_toggle_mode(true); |
| 779 | key_scale_button->set_pressed(false); |
| 780 | key_scale_button->set_focus_mode(FOCUS_NONE); |
| 781 | key_scale_button->set_tooltip_text(TTR("Scale mask for inserting keys." )); |
| 782 | animation_hb->add_child(key_scale_button); |
| 783 | |
| 784 | key_insert_button = memnew(Button); |
| 785 | key_insert_button->set_flat(true); |
| 786 | key_insert_button->set_focus_mode(FOCUS_NONE); |
| 787 | key_insert_button->connect("pressed" , callable_mp(this, &Skeleton3DEditor::insert_keys).bind(false)); |
| 788 | key_insert_button->set_tooltip_text(TTR("Insert key of bone poses already exist track." )); |
| 789 | key_insert_button->set_shortcut(ED_SHORTCUT("skeleton_3d_editor/insert_key_to_existing_tracks" , TTR("Insert Key (Existing Tracks)" ), Key::INSERT)); |
| 790 | animation_hb->add_child(key_insert_button); |
| 791 | |
| 792 | key_insert_all_button = memnew(Button); |
| 793 | key_insert_all_button->set_flat(true); |
| 794 | key_insert_all_button->set_focus_mode(FOCUS_NONE); |
| 795 | key_insert_all_button->connect("pressed" , callable_mp(this, &Skeleton3DEditor::insert_keys).bind(true)); |
| 796 | key_insert_all_button->set_tooltip_text(TTR("Insert key of all bone poses." )); |
| 797 | key_insert_all_button->set_shortcut(ED_SHORTCUT("skeleton_3d_editor/insert_key_of_all_bones" , TTR("Insert Key (All Bones)" ), KeyModifierMask::CMD_OR_CTRL + Key::INSERT)); |
| 798 | animation_hb->add_child(key_insert_all_button); |
| 799 | |
| 800 | // Bone tree. |
| 801 | bones_section = memnew(EditorInspectorSection); |
| 802 | bones_section->setup("bones" , "Bones" , skeleton, Color(0.0f, 0.0, 0.0f), true); |
| 803 | add_child(bones_section); |
| 804 | bones_section->unfold(); |
| 805 | |
| 806 | ScrollContainer *s_con = memnew(ScrollContainer); |
| 807 | s_con->set_h_size_flags(SIZE_EXPAND_FILL); |
| 808 | s_con->set_custom_minimum_size(Size2(1, 350) * EDSCALE); |
| 809 | bones_section->get_vbox()->add_child(s_con); |
| 810 | |
| 811 | joint_tree = memnew(Tree); |
| 812 | joint_tree->set_columns(1); |
| 813 | joint_tree->set_focus_mode(Control::FOCUS_NONE); |
| 814 | joint_tree->set_select_mode(Tree::SELECT_SINGLE); |
| 815 | joint_tree->set_hide_root(true); |
| 816 | joint_tree->set_v_size_flags(SIZE_EXPAND_FILL); |
| 817 | joint_tree->set_h_size_flags(SIZE_EXPAND_FILL); |
| 818 | joint_tree->set_allow_rmb_select(true); |
| 819 | SET_DRAG_FORWARDING_GCD(joint_tree, Skeleton3DEditor); |
| 820 | s_con->add_child(joint_tree); |
| 821 | |
| 822 | pose_editor = memnew(BoneTransformEditor(skeleton)); |
| 823 | pose_editor->set_label(TTR("Bone Transform" )); |
| 824 | pose_editor->set_visible(false); |
| 825 | add_child(pose_editor); |
| 826 | |
| 827 | set_keyable(te->has_keying()); |
| 828 | } |
| 829 | |
| 830 | void Skeleton3DEditor::_notification(int p_what) { |
| 831 | switch (p_what) { |
| 832 | case NOTIFICATION_ENTER_TREE: { |
| 833 | update_joint_tree(); |
| 834 | |
| 835 | joint_tree->connect("item_selected" , callable_mp(this, &Skeleton3DEditor::_joint_tree_selection_changed)); |
| 836 | joint_tree->connect("item_mouse_selected" , callable_mp(this, &Skeleton3DEditor::_joint_tree_rmb_select)); |
| 837 | #ifdef TOOLS_ENABLED |
| 838 | skeleton->connect("pose_updated" , callable_mp(this, &Skeleton3DEditor::_draw_gizmo)); |
| 839 | skeleton->connect("pose_updated" , callable_mp(this, &Skeleton3DEditor::_update_properties)); |
| 840 | skeleton->connect("bone_enabled_changed" , callable_mp(this, &Skeleton3DEditor::_bone_enabled_changed)); |
| 841 | skeleton->connect("show_rest_only_changed" , callable_mp(this, &Skeleton3DEditor::_update_gizmo_visible)); |
| 842 | #endif |
| 843 | |
| 844 | get_tree()->connect("node_removed" , callable_mp(this, &Skeleton3DEditor::_node_removed), Object::CONNECT_ONE_SHOT); |
| 845 | } break; |
| 846 | case NOTIFICATION_READY: { |
| 847 | // Will trigger NOTIFICATION_THEME_CHANGED, but won't cause any loops if called here. |
| 848 | add_theme_constant_override("separation" , 0); |
| 849 | } break; |
| 850 | case NOTIFICATION_THEME_CHANGED: { |
| 851 | skeleton_options->set_icon(get_editor_theme_icon(SNAME("Skeleton3D" ))); |
| 852 | edit_mode_button->set_icon(get_editor_theme_icon(SNAME("ToolBoneSelect" ))); |
| 853 | key_loc_button->set_icon(get_editor_theme_icon(SNAME("KeyPosition" ))); |
| 854 | key_rot_button->set_icon(get_editor_theme_icon(SNAME("KeyRotation" ))); |
| 855 | key_scale_button->set_icon(get_editor_theme_icon(SNAME("KeyScale" ))); |
| 856 | key_insert_button->set_icon(get_editor_theme_icon(SNAME("Key" ))); |
| 857 | key_insert_all_button->set_icon(get_editor_theme_icon(SNAME("NewKey" ))); |
| 858 | bones_section->set_bg_color(get_theme_color(SNAME("prop_subsection" ), EditorStringName(Editor))); |
| 859 | |
| 860 | update_joint_tree(); |
| 861 | } break; |
| 862 | case NOTIFICATION_PREDELETE: { |
| 863 | if (skeleton) { |
| 864 | select_bone(-1); // Requires that the joint_tree has not been deleted. |
| 865 | #ifdef TOOLS_ENABLED |
| 866 | skeleton->disconnect("show_rest_only_changed" , callable_mp(this, &Skeleton3DEditor::_update_gizmo_visible)); |
| 867 | skeleton->disconnect("bone_enabled_changed" , callable_mp(this, &Skeleton3DEditor::_bone_enabled_changed)); |
| 868 | skeleton->disconnect("pose_updated" , callable_mp(this, &Skeleton3DEditor::_draw_gizmo)); |
| 869 | skeleton->disconnect("pose_updated" , callable_mp(this, &Skeleton3DEditor::_update_properties)); |
| 870 | skeleton->set_transform_gizmo_visible(true); |
| 871 | #endif |
| 872 | handles_mesh_instance->get_parent()->remove_child(handles_mesh_instance); |
| 873 | } |
| 874 | edit_mode_toggled(false); |
| 875 | } break; |
| 876 | } |
| 877 | } |
| 878 | |
| 879 | void Skeleton3DEditor::_node_removed(Node *p_node) { |
| 880 | if (skeleton && p_node == skeleton) { |
| 881 | skeleton = nullptr; |
| 882 | skeleton_options->hide(); |
| 883 | } |
| 884 | |
| 885 | _update_properties(); |
| 886 | } |
| 887 | |
| 888 | void Skeleton3DEditor::_bind_methods() { |
| 889 | ClassDB::bind_method(D_METHOD("_node_removed" ), &Skeleton3DEditor::_node_removed); |
| 890 | ClassDB::bind_method(D_METHOD("_joint_tree_selection_changed" ), &Skeleton3DEditor::_joint_tree_selection_changed); |
| 891 | ClassDB::bind_method(D_METHOD("_joint_tree_rmb_select" ), &Skeleton3DEditor::_joint_tree_rmb_select); |
| 892 | ClassDB::bind_method(D_METHOD("_update_properties" ), &Skeleton3DEditor::_update_properties); |
| 893 | ClassDB::bind_method(D_METHOD("_on_click_skeleton_option" ), &Skeleton3DEditor::_on_click_skeleton_option); |
| 894 | |
| 895 | ClassDB::bind_method(D_METHOD("move_skeleton_bone" ), &Skeleton3DEditor::move_skeleton_bone); |
| 896 | |
| 897 | ClassDB::bind_method(D_METHOD("_draw_gizmo" ), &Skeleton3DEditor::_draw_gizmo); |
| 898 | } |
| 899 | |
| 900 | void Skeleton3DEditor::edit_mode_toggled(const bool pressed) { |
| 901 | edit_mode = pressed; |
| 902 | _update_gizmo_visible(); |
| 903 | } |
| 904 | |
| 905 | Skeleton3DEditor::Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, Skeleton3D *p_skeleton) : |
| 906 | editor_plugin(e_plugin), |
| 907 | skeleton(p_skeleton) { |
| 908 | singleton = this; |
| 909 | |
| 910 | // Handle. |
| 911 | handle_material = Ref<ShaderMaterial>(memnew(ShaderMaterial)); |
| 912 | handle_shader = Ref<Shader>(memnew(Shader)); |
| 913 | handle_shader->set_code(R"( |
| 914 | // Skeleton 3D gizmo handle shader. |
| 915 | |
| 916 | shader_type spatial; |
| 917 | render_mode unshaded, shadows_disabled, depth_draw_always; |
| 918 | uniform sampler2D texture_albedo : source_color; |
| 919 | uniform float point_size : hint_range(0,128) = 32; |
| 920 | void vertex() { |
| 921 | if (!OUTPUT_IS_SRGB) { |
| 922 | COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); |
| 923 | } |
| 924 | VERTEX = VERTEX; |
| 925 | POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0); |
| 926 | POSITION.z = mix(POSITION.z, 0, 0.999); |
| 927 | POINT_SIZE = point_size; |
| 928 | } |
| 929 | void fragment() { |
| 930 | vec4 albedo_tex = texture(texture_albedo,POINT_COORD); |
| 931 | vec3 col = albedo_tex.rgb + COLOR.rgb; |
| 932 | col = vec3(min(col.r,1.0),min(col.g,1.0),min(col.b,1.0)); |
| 933 | ALBEDO = col; |
| 934 | if (albedo_tex.a < 0.5) { discard; } |
| 935 | ALPHA = albedo_tex.a; |
| 936 | } |
| 937 | )" ); |
| 938 | handle_material->set_shader(handle_shader); |
| 939 | Ref<Texture2D> handle = EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("EditorBoneHandle" ), EditorStringName(EditorIcons)); |
| 940 | handle_material->set_shader_parameter("point_size" , handle->get_width()); |
| 941 | handle_material->set_shader_parameter("texture_albedo" , handle); |
| 942 | |
| 943 | handles_mesh_instance = memnew(MeshInstance3D); |
| 944 | handles_mesh_instance->set_cast_shadows_setting(GeometryInstance3D::SHADOW_CASTING_SETTING_OFF); |
| 945 | handles_mesh.instantiate(); |
| 946 | handles_mesh_instance->set_mesh(handles_mesh); |
| 947 | |
| 948 | create_editors(); |
| 949 | } |
| 950 | |
| 951 | void Skeleton3DEditor::update_bone_original() { |
| 952 | if (!skeleton) { |
| 953 | return; |
| 954 | } |
| 955 | if (skeleton->get_bone_count() == 0 || selected_bone == -1) { |
| 956 | return; |
| 957 | } |
| 958 | bone_original_position = skeleton->get_bone_pose_position(selected_bone); |
| 959 | bone_original_rotation = skeleton->get_bone_pose_rotation(selected_bone); |
| 960 | bone_original_scale = skeleton->get_bone_pose_scale(selected_bone); |
| 961 | } |
| 962 | |
| 963 | void Skeleton3DEditor::_hide_handles() { |
| 964 | handles_mesh_instance->hide(); |
| 965 | } |
| 966 | |
| 967 | void Skeleton3DEditor::_draw_gizmo() { |
| 968 | if (!skeleton) { |
| 969 | return; |
| 970 | } |
| 971 | |
| 972 | // If you call get_bone_global_pose() while drawing the surface, such as toggle rest mode, |
| 973 | // the skeleton update will be done first and |
| 974 | // the drawing surface will be interrupted once and an error will occur. |
| 975 | skeleton->force_update_all_dirty_bones(); |
| 976 | |
| 977 | // Handles. |
| 978 | if (edit_mode) { |
| 979 | _draw_handles(); |
| 980 | } else { |
| 981 | _hide_handles(); |
| 982 | } |
| 983 | } |
| 984 | |
| 985 | void Skeleton3DEditor::_draw_handles() { |
| 986 | const int bone_count = skeleton->get_bone_count(); |
| 987 | |
| 988 | handles_mesh->clear_surfaces(); |
| 989 | |
| 990 | if (bone_count) { |
| 991 | handles_mesh_instance->show(); |
| 992 | |
| 993 | handles_mesh->surface_begin(Mesh::PRIMITIVE_POINTS); |
| 994 | |
| 995 | for (int i = 0; i < bone_count; i++) { |
| 996 | Color c; |
| 997 | if (i == selected_bone) { |
| 998 | c = Color(1, 1, 0); |
| 999 | } else { |
| 1000 | c = Color(0.1, 0.25, 0.8); |
| 1001 | } |
| 1002 | Vector3 point = skeleton->get_bone_global_pose(i).origin; |
| 1003 | handles_mesh->surface_set_color(c); |
| 1004 | handles_mesh->surface_add_vertex(point); |
| 1005 | } |
| 1006 | handles_mesh->surface_end(); |
| 1007 | handles_mesh->surface_set_material(0, handle_material); |
| 1008 | } else { |
| 1009 | handles_mesh_instance->hide(); |
| 1010 | } |
| 1011 | } |
| 1012 | |
| 1013 | TreeItem *Skeleton3DEditor::_find(TreeItem *p_node, const NodePath &p_path) { |
| 1014 | if (!p_node) { |
| 1015 | return nullptr; |
| 1016 | } |
| 1017 | |
| 1018 | NodePath np = p_node->get_metadata(0); |
| 1019 | if (np == p_path) { |
| 1020 | return p_node; |
| 1021 | } |
| 1022 | |
| 1023 | TreeItem *children = p_node->get_first_child(); |
| 1024 | while (children) { |
| 1025 | TreeItem *n = _find(children, p_path); |
| 1026 | if (n) { |
| 1027 | return n; |
| 1028 | } |
| 1029 | children = children->get_next(); |
| 1030 | } |
| 1031 | |
| 1032 | return nullptr; |
| 1033 | } |
| 1034 | |
| 1035 | void Skeleton3DEditor::_subgizmo_selection_change() { |
| 1036 | if (!skeleton) { |
| 1037 | return; |
| 1038 | } |
| 1039 | |
| 1040 | // Once validated by subgizmos_intersect_ray, but required if through inspector's bones tree. |
| 1041 | if (!edit_mode) { |
| 1042 | skeleton->clear_subgizmo_selection(); |
| 1043 | return; |
| 1044 | } |
| 1045 | |
| 1046 | int selected = -1; |
| 1047 | Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); |
| 1048 | if (se) { |
| 1049 | selected = se->get_selected_bone(); |
| 1050 | } |
| 1051 | |
| 1052 | if (selected >= 0) { |
| 1053 | Vector<Ref<Node3DGizmo>> gizmos = skeleton->get_gizmos(); |
| 1054 | for (int i = 0; i < gizmos.size(); i++) { |
| 1055 | Ref<EditorNode3DGizmo> gizmo = gizmos[i]; |
| 1056 | if (!gizmo.is_valid()) { |
| 1057 | continue; |
| 1058 | } |
| 1059 | Ref<Skeleton3DGizmoPlugin> plugin = gizmo->get_plugin(); |
| 1060 | if (!plugin.is_valid()) { |
| 1061 | continue; |
| 1062 | } |
| 1063 | skeleton->set_subgizmo_selection(gizmo, selected, skeleton->get_bone_global_pose(selected)); |
| 1064 | break; |
| 1065 | } |
| 1066 | } else { |
| 1067 | skeleton->clear_subgizmo_selection(); |
| 1068 | } |
| 1069 | } |
| 1070 | |
| 1071 | void Skeleton3DEditor::select_bone(int p_idx) { |
| 1072 | if (p_idx >= 0) { |
| 1073 | TreeItem *ti = _find(joint_tree->get_root(), "bones/" + itos(p_idx)); |
| 1074 | if (ti) { |
| 1075 | // Make visible when it's collapsed. |
| 1076 | TreeItem *node = ti->get_parent(); |
| 1077 | while (node && node != joint_tree->get_root()) { |
| 1078 | node->set_collapsed(false); |
| 1079 | node = node->get_parent(); |
| 1080 | } |
| 1081 | ti->select(0); |
| 1082 | joint_tree->scroll_to_item(ti); |
| 1083 | } |
| 1084 | } else { |
| 1085 | selected_bone = -1; |
| 1086 | joint_tree->deselect_all(); |
| 1087 | _joint_tree_selection_changed(); |
| 1088 | } |
| 1089 | } |
| 1090 | |
| 1091 | Skeleton3DEditor::~Skeleton3DEditor() { |
| 1092 | singleton = nullptr; |
| 1093 | |
| 1094 | handles_mesh_instance->queue_free(); |
| 1095 | |
| 1096 | Node3DEditor *ne = Node3DEditor::get_singleton(); |
| 1097 | |
| 1098 | if (animation_hb) { |
| 1099 | ne->remove_control_from_menu_panel(animation_hb); |
| 1100 | memdelete(animation_hb); |
| 1101 | } |
| 1102 | |
| 1103 | if (separator) { |
| 1104 | ne->remove_control_from_menu_panel(separator); |
| 1105 | memdelete(separator); |
| 1106 | } |
| 1107 | |
| 1108 | if (skeleton_options) { |
| 1109 | ne->remove_control_from_menu_panel(skeleton_options); |
| 1110 | memdelete(skeleton_options); |
| 1111 | } |
| 1112 | |
| 1113 | if (edit_mode_button) { |
| 1114 | ne->remove_control_from_menu_panel(edit_mode_button); |
| 1115 | memdelete(edit_mode_button); |
| 1116 | } |
| 1117 | } |
| 1118 | |
| 1119 | bool EditorInspectorPluginSkeleton::can_handle(Object *p_object) { |
| 1120 | return Object::cast_to<Skeleton3D>(p_object) != nullptr; |
| 1121 | } |
| 1122 | |
| 1123 | void EditorInspectorPluginSkeleton::parse_begin(Object *p_object) { |
| 1124 | Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_object); |
| 1125 | ERR_FAIL_NULL(skeleton); |
| 1126 | |
| 1127 | skel_editor = memnew(Skeleton3DEditor(this, skeleton)); |
| 1128 | add_custom_control(skel_editor); |
| 1129 | } |
| 1130 | |
| 1131 | Skeleton3DEditorPlugin::Skeleton3DEditorPlugin() { |
| 1132 | skeleton_plugin = memnew(EditorInspectorPluginSkeleton); |
| 1133 | |
| 1134 | EditorInspector::add_inspector_plugin(skeleton_plugin); |
| 1135 | |
| 1136 | Ref<Skeleton3DGizmoPlugin> gizmo_plugin = Ref<Skeleton3DGizmoPlugin>(memnew(Skeleton3DGizmoPlugin)); |
| 1137 | Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin); |
| 1138 | } |
| 1139 | |
| 1140 | EditorPlugin::AfterGUIInput Skeleton3DEditorPlugin::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { |
| 1141 | Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); |
| 1142 | Node3DEditor *ne = Node3DEditor::get_singleton(); |
| 1143 | if (se && se->is_edit_mode()) { |
| 1144 | const Ref<InputEventMouseButton> mb = p_event; |
| 1145 | if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { |
| 1146 | if (ne->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) { |
| 1147 | if (!ne->is_gizmo_visible()) { |
| 1148 | return EditorPlugin::AFTER_GUI_INPUT_STOP; |
| 1149 | } |
| 1150 | } |
| 1151 | if (mb->is_pressed()) { |
| 1152 | se->update_bone_original(); |
| 1153 | } |
| 1154 | } |
| 1155 | return EditorPlugin::AFTER_GUI_INPUT_CUSTOM; |
| 1156 | } |
| 1157 | return EditorPlugin::AFTER_GUI_INPUT_PASS; |
| 1158 | } |
| 1159 | |
| 1160 | bool Skeleton3DEditorPlugin::handles(Object *p_object) const { |
| 1161 | return p_object->is_class("Skeleton3D" ); |
| 1162 | } |
| 1163 | |
| 1164 | void Skeleton3DEditor::_bone_enabled_changed(const int p_bone_id) { |
| 1165 | _update_gizmo_visible(); |
| 1166 | } |
| 1167 | |
| 1168 | void Skeleton3DEditor::_update_gizmo_visible() { |
| 1169 | _subgizmo_selection_change(); |
| 1170 | if (edit_mode) { |
| 1171 | if (selected_bone == -1) { |
| 1172 | #ifdef TOOLS_ENABLED |
| 1173 | skeleton->set_transform_gizmo_visible(false); |
| 1174 | #endif |
| 1175 | } else { |
| 1176 | #ifdef TOOLS_ENABLED |
| 1177 | if (skeleton->is_bone_enabled(selected_bone) && !skeleton->is_show_rest_only()) { |
| 1178 | skeleton->set_transform_gizmo_visible(true); |
| 1179 | } else { |
| 1180 | skeleton->set_transform_gizmo_visible(false); |
| 1181 | } |
| 1182 | #endif |
| 1183 | } |
| 1184 | } else { |
| 1185 | #ifdef TOOLS_ENABLED |
| 1186 | skeleton->set_transform_gizmo_visible(true); |
| 1187 | #endif |
| 1188 | } |
| 1189 | _draw_gizmo(); |
| 1190 | } |
| 1191 | |
| 1192 | int Skeleton3DEditor::get_selected_bone() const { |
| 1193 | return selected_bone; |
| 1194 | } |
| 1195 | |
| 1196 | Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() { |
| 1197 | unselected_mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); |
| 1198 | unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); |
| 1199 | unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); |
| 1200 | unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); |
| 1201 | unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); |
| 1202 | |
| 1203 | selected_mat = Ref<ShaderMaterial>(memnew(ShaderMaterial)); |
| 1204 | selected_sh = Ref<Shader>(memnew(Shader)); |
| 1205 | selected_sh->set_code(R"( |
| 1206 | // Skeleton 3D gizmo bones shader. |
| 1207 | |
| 1208 | shader_type spatial; |
| 1209 | render_mode unshaded, shadows_disabled; |
| 1210 | void vertex() { |
| 1211 | if (!OUTPUT_IS_SRGB) { |
| 1212 | COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); |
| 1213 | } |
| 1214 | VERTEX = VERTEX; |
| 1215 | POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0); |
| 1216 | POSITION.z = mix(POSITION.z, 0, 0.998); |
| 1217 | } |
| 1218 | void fragment() { |
| 1219 | ALBEDO = COLOR.rgb; |
| 1220 | ALPHA = COLOR.a; |
| 1221 | } |
| 1222 | )" ); |
| 1223 | selected_mat->set_shader(selected_sh); |
| 1224 | |
| 1225 | // Register properties in editor settings. |
| 1226 | EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton" , Color(1, 0.8, 0.4)); |
| 1227 | EDITOR_DEF("editors/3d_gizmos/gizmo_colors/selected_bone" , Color(0.8, 0.3, 0.0)); |
| 1228 | EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_axis_length" , (float)0.1); |
| 1229 | EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_shape" , 1); |
| 1230 | EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/3d_gizmos/gizmo_settings/bone_shape" , PROPERTY_HINT_ENUM, "Wire,Octahedron" )); |
| 1231 | } |
| 1232 | |
| 1233 | bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { |
| 1234 | return Object::cast_to<Skeleton3D>(p_spatial) != nullptr; |
| 1235 | } |
| 1236 | |
| 1237 | String Skeleton3DGizmoPlugin::get_gizmo_name() const { |
| 1238 | return "Skeleton3D" ; |
| 1239 | } |
| 1240 | |
| 1241 | int Skeleton3DGizmoPlugin::get_priority() const { |
| 1242 | return -1; |
| 1243 | } |
| 1244 | |
| 1245 | int Skeleton3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const { |
| 1246 | Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); |
| 1247 | ERR_FAIL_NULL_V(skeleton, -1); |
| 1248 | |
| 1249 | Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); |
| 1250 | |
| 1251 | if (!se || !se->is_edit_mode()) { |
| 1252 | return -1; |
| 1253 | } |
| 1254 | |
| 1255 | if (Node3DEditor::get_singleton()->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) { |
| 1256 | return -1; |
| 1257 | } |
| 1258 | |
| 1259 | // Select bone. |
| 1260 | real_t grab_threshold = 4 * EDSCALE; |
| 1261 | Vector3 ray_from = p_camera->get_global_transform().origin; |
| 1262 | Transform3D gt = skeleton->get_global_transform(); |
| 1263 | int closest_idx = -1; |
| 1264 | real_t closest_dist = 1e10; |
| 1265 | const int bone_count = skeleton->get_bone_count(); |
| 1266 | for (int i = 0; i < bone_count; i++) { |
| 1267 | Vector3 joint_pos_3d = gt.xform(skeleton->get_bone_global_pose(i).origin); |
| 1268 | Vector2 joint_pos_2d = p_camera->unproject_position(joint_pos_3d); |
| 1269 | real_t dist_3d = ray_from.distance_to(joint_pos_3d); |
| 1270 | real_t dist_2d = p_point.distance_to(joint_pos_2d); |
| 1271 | if (dist_2d < grab_threshold && dist_3d < closest_dist) { |
| 1272 | closest_dist = dist_3d; |
| 1273 | closest_idx = i; |
| 1274 | } |
| 1275 | } |
| 1276 | |
| 1277 | if (closest_idx >= 0) { |
| 1278 | se->select_bone(closest_idx); |
| 1279 | return closest_idx; |
| 1280 | } |
| 1281 | |
| 1282 | se->select_bone(-1); |
| 1283 | return -1; |
| 1284 | } |
| 1285 | |
| 1286 | Transform3D Skeleton3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const { |
| 1287 | Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); |
| 1288 | ERR_FAIL_NULL_V(skeleton, Transform3D()); |
| 1289 | |
| 1290 | return skeleton->get_bone_global_pose(p_id); |
| 1291 | } |
| 1292 | |
| 1293 | void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) { |
| 1294 | Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); |
| 1295 | ERR_FAIL_NULL(skeleton); |
| 1296 | |
| 1297 | // Prepare for global to local. |
| 1298 | Transform3D original_to_local; |
| 1299 | int parent_idx = skeleton->get_bone_parent(p_id); |
| 1300 | if (parent_idx >= 0) { |
| 1301 | original_to_local = skeleton->get_bone_global_pose(parent_idx); |
| 1302 | } |
| 1303 | Basis to_local = original_to_local.get_basis().inverse(); |
| 1304 | |
| 1305 | // Prepare transform. |
| 1306 | Transform3D t; |
| 1307 | |
| 1308 | // Basis. |
| 1309 | t.basis = to_local * p_transform.get_basis(); |
| 1310 | |
| 1311 | // Origin. |
| 1312 | Vector3 orig = skeleton->get_bone_pose(p_id).origin; |
| 1313 | Vector3 sub = p_transform.origin - skeleton->get_bone_global_pose(p_id).origin; |
| 1314 | t.origin = orig + to_local.xform(sub); |
| 1315 | |
| 1316 | // Apply transform. |
| 1317 | skeleton->set_bone_pose_position(p_id, t.origin); |
| 1318 | skeleton->set_bone_pose_rotation(p_id, t.basis.get_rotation_quaternion()); |
| 1319 | skeleton->set_bone_pose_scale(p_id, t.basis.get_scale()); |
| 1320 | } |
| 1321 | |
| 1322 | void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) { |
| 1323 | Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); |
| 1324 | ERR_FAIL_NULL(skeleton); |
| 1325 | |
| 1326 | Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); |
| 1327 | Node3DEditor *ne = Node3DEditor::get_singleton(); |
| 1328 | |
| 1329 | EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); |
| 1330 | ur->create_action(TTR("Set Bone Transform" )); |
| 1331 | if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || ne->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE) { |
| 1332 | for (int i = 0; i < p_ids.size(); i++) { |
| 1333 | ur->add_do_method(skeleton, "set_bone_pose_position" , p_ids[i], skeleton->get_bone_pose_position(p_ids[i])); |
| 1334 | ur->add_undo_method(skeleton, "set_bone_pose_position" , p_ids[i], se->get_bone_original_position()); |
| 1335 | } |
| 1336 | } |
| 1337 | if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || ne->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) { |
| 1338 | for (int i = 0; i < p_ids.size(); i++) { |
| 1339 | ur->add_do_method(skeleton, "set_bone_pose_rotation" , p_ids[i], skeleton->get_bone_pose_rotation(p_ids[i])); |
| 1340 | ur->add_undo_method(skeleton, "set_bone_pose_rotation" , p_ids[i], se->get_bone_original_rotation()); |
| 1341 | } |
| 1342 | } |
| 1343 | if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE) { |
| 1344 | for (int i = 0; i < p_ids.size(); i++) { |
| 1345 | // If the axis is swapped by scaling, the rotation can be changed. |
| 1346 | ur->add_do_method(skeleton, "set_bone_pose_rotation" , p_ids[i], skeleton->get_bone_pose_rotation(p_ids[i])); |
| 1347 | ur->add_undo_method(skeleton, "set_bone_pose_rotation" , p_ids[i], se->get_bone_original_rotation()); |
| 1348 | ur->add_do_method(skeleton, "set_bone_pose_scale" , p_ids[i], skeleton->get_bone_pose_scale(p_ids[i])); |
| 1349 | ur->add_undo_method(skeleton, "set_bone_pose_scale" , p_ids[i], se->get_bone_original_scale()); |
| 1350 | } |
| 1351 | } |
| 1352 | ur->commit_action(); |
| 1353 | } |
| 1354 | |
| 1355 | void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { |
| 1356 | Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d()); |
| 1357 | p_gizmo->clear(); |
| 1358 | |
| 1359 | if (!skeleton->get_bone_count()) { |
| 1360 | return; |
| 1361 | } |
| 1362 | |
| 1363 | int selected = -1; |
| 1364 | Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); |
| 1365 | if (se) { |
| 1366 | selected = se->get_selected_bone(); |
| 1367 | } |
| 1368 | |
| 1369 | Color bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/skeleton" ); |
| 1370 | Color selected_bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/selected_bone" ); |
| 1371 | real_t bone_axis_length = EDITOR_GET("editors/3d_gizmos/gizmo_settings/bone_axis_length" ); |
| 1372 | int bone_shape = EDITOR_GET("editors/3d_gizmos/gizmo_settings/bone_shape" ); |
| 1373 | |
| 1374 | LocalVector<Color> axis_colors; |
| 1375 | axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_x_color" ), EditorStringName(Editor))); |
| 1376 | axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_y_color" ), EditorStringName(Editor))); |
| 1377 | axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_z_color" ), EditorStringName(Editor))); |
| 1378 | |
| 1379 | Ref<SurfaceTool> surface_tool(memnew(SurfaceTool)); |
| 1380 | surface_tool->begin(Mesh::PRIMITIVE_LINES); |
| 1381 | |
| 1382 | if (p_gizmo->is_selected()) { |
| 1383 | surface_tool->set_material(selected_mat); |
| 1384 | } else { |
| 1385 | unselected_mat->set_albedo(bone_color); |
| 1386 | surface_tool->set_material(unselected_mat); |
| 1387 | } |
| 1388 | |
| 1389 | LocalVector<int> bones; |
| 1390 | LocalVector<float> weights; |
| 1391 | bones.resize(4); |
| 1392 | weights.resize(4); |
| 1393 | for (int i = 0; i < 4; i++) { |
| 1394 | bones[i] = 0; |
| 1395 | weights[i] = 0; |
| 1396 | } |
| 1397 | weights[0] = 1; |
| 1398 | |
| 1399 | int current_bone_index = 0; |
| 1400 | Vector<int> bones_to_process = skeleton->get_parentless_bones(); |
| 1401 | |
| 1402 | while (bones_to_process.size() > current_bone_index) { |
| 1403 | int current_bone_idx = bones_to_process[current_bone_index]; |
| 1404 | current_bone_index++; |
| 1405 | |
| 1406 | Color current_bone_color = (current_bone_idx == selected) ? selected_bone_color : bone_color; |
| 1407 | |
| 1408 | Vector<int> child_bones_vector; |
| 1409 | child_bones_vector = skeleton->get_bone_children(current_bone_idx); |
| 1410 | int child_bones_size = child_bones_vector.size(); |
| 1411 | |
| 1412 | for (int i = 0; i < child_bones_size; i++) { |
| 1413 | // Something wrong. |
| 1414 | if (child_bones_vector[i] < 0) { |
| 1415 | continue; |
| 1416 | } |
| 1417 | |
| 1418 | int child_bone_idx = child_bones_vector[i]; |
| 1419 | |
| 1420 | Vector3 v0 = skeleton->get_bone_global_rest(current_bone_idx).origin; |
| 1421 | Vector3 v1 = skeleton->get_bone_global_rest(child_bone_idx).origin; |
| 1422 | Vector3 d = (v1 - v0).normalized(); |
| 1423 | real_t dist = v0.distance_to(v1); |
| 1424 | |
| 1425 | // Find closest axis. |
| 1426 | int closest = -1; |
| 1427 | real_t closest_d = 0.0; |
| 1428 | for (int j = 0; j < 3; j++) { |
| 1429 | real_t dp = Math::abs(skeleton->get_bone_global_rest(current_bone_idx).basis[j].normalized().dot(d)); |
| 1430 | if (j == 0 || dp > closest_d) { |
| 1431 | closest = j; |
| 1432 | } |
| 1433 | } |
| 1434 | |
| 1435 | // Draw bone. |
| 1436 | switch (bone_shape) { |
| 1437 | case 0: { // Wire shape. |
| 1438 | surface_tool->set_color(current_bone_color); |
| 1439 | bones[0] = current_bone_idx; |
| 1440 | surface_tool->set_bones(bones); |
| 1441 | surface_tool->set_weights(weights); |
| 1442 | surface_tool->add_vertex(v0); |
| 1443 | bones[0] = child_bone_idx; |
| 1444 | surface_tool->set_bones(bones); |
| 1445 | surface_tool->set_weights(weights); |
| 1446 | surface_tool->add_vertex(v1); |
| 1447 | } break; |
| 1448 | |
| 1449 | case 1: { // Octahedron shape. |
| 1450 | Vector3 first; |
| 1451 | Vector3 points[6]; |
| 1452 | int point_idx = 0; |
| 1453 | for (int j = 0; j < 3; j++) { |
| 1454 | Vector3 axis; |
| 1455 | if (first == Vector3()) { |
| 1456 | axis = d.cross(d.cross(skeleton->get_bone_global_rest(current_bone_idx).basis[j])).normalized(); |
| 1457 | first = axis; |
| 1458 | } else { |
| 1459 | axis = d.cross(first).normalized(); |
| 1460 | } |
| 1461 | |
| 1462 | surface_tool->set_color(current_bone_color); |
| 1463 | for (int k = 0; k < 2; k++) { |
| 1464 | if (k == 1) { |
| 1465 | axis = -axis; |
| 1466 | } |
| 1467 | Vector3 point = v0 + d * dist * 0.2; |
| 1468 | point += axis * dist * 0.1; |
| 1469 | |
| 1470 | bones[0] = current_bone_idx; |
| 1471 | surface_tool->set_bones(bones); |
| 1472 | surface_tool->set_weights(weights); |
| 1473 | surface_tool->add_vertex(v0); |
| 1474 | surface_tool->set_bones(bones); |
| 1475 | surface_tool->set_weights(weights); |
| 1476 | surface_tool->add_vertex(point); |
| 1477 | |
| 1478 | surface_tool->set_bones(bones); |
| 1479 | surface_tool->set_weights(weights); |
| 1480 | surface_tool->add_vertex(point); |
| 1481 | bones[0] = child_bone_idx; |
| 1482 | surface_tool->set_bones(bones); |
| 1483 | surface_tool->set_weights(weights); |
| 1484 | surface_tool->add_vertex(v1); |
| 1485 | points[point_idx++] = point; |
| 1486 | } |
| 1487 | } |
| 1488 | surface_tool->set_color(current_bone_color); |
| 1489 | SWAP(points[1], points[2]); |
| 1490 | bones[0] = current_bone_idx; |
| 1491 | for (int j = 0; j < 6; j++) { |
| 1492 | surface_tool->set_bones(bones); |
| 1493 | surface_tool->set_weights(weights); |
| 1494 | surface_tool->add_vertex(points[j]); |
| 1495 | surface_tool->set_bones(bones); |
| 1496 | surface_tool->set_weights(weights); |
| 1497 | surface_tool->add_vertex(points[(j + 1) % 6]); |
| 1498 | } |
| 1499 | } break; |
| 1500 | } |
| 1501 | |
| 1502 | // Axis as root of the bone. |
| 1503 | for (int j = 0; j < 3; j++) { |
| 1504 | bones[0] = current_bone_idx; |
| 1505 | surface_tool->set_color(axis_colors[j]); |
| 1506 | surface_tool->set_bones(bones); |
| 1507 | surface_tool->set_weights(weights); |
| 1508 | surface_tool->add_vertex(v0); |
| 1509 | surface_tool->set_bones(bones); |
| 1510 | surface_tool->set_weights(weights); |
| 1511 | surface_tool->add_vertex(v0 + (skeleton->get_bone_global_rest(current_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length); |
| 1512 | |
| 1513 | if (j == closest) { |
| 1514 | continue; |
| 1515 | } |
| 1516 | } |
| 1517 | |
| 1518 | // Axis at the end of the bone children. |
| 1519 | if (i == child_bones_size - 1) { |
| 1520 | for (int j = 0; j < 3; j++) { |
| 1521 | bones[0] = child_bone_idx; |
| 1522 | surface_tool->set_color(axis_colors[j]); |
| 1523 | surface_tool->set_bones(bones); |
| 1524 | surface_tool->set_weights(weights); |
| 1525 | surface_tool->add_vertex(v1); |
| 1526 | surface_tool->set_bones(bones); |
| 1527 | surface_tool->set_weights(weights); |
| 1528 | surface_tool->add_vertex(v1 + (skeleton->get_bone_global_rest(child_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length); |
| 1529 | |
| 1530 | if (j == closest) { |
| 1531 | continue; |
| 1532 | } |
| 1533 | } |
| 1534 | } |
| 1535 | |
| 1536 | // Add the bone's children to the list of bones to be processed. |
| 1537 | bones_to_process.push_back(child_bones_vector[i]); |
| 1538 | } |
| 1539 | } |
| 1540 | |
| 1541 | Ref<ArrayMesh> m = surface_tool->commit(); |
| 1542 | p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(skeleton->create_skin_from_rest_transforms())); |
| 1543 | } |
| 1544 | |